Writing tests for a multi-tenant, multi-subdomain app turns out to be very tricky to figure out, e.g.:
I saw that, and understood the frustration. Integration tests (“request specs” or “feature specs“) are built on a stack of frequently changing libraries and shifting API’s. And the recipe for subdomain-aware testing isn’t documented in any particular tool’s notes. So here’s my working configuration for Rails 5.2.0, in mid-2018.
The goal: feature- and request-specs to specific hostnames
I’m creating a set of sites with laws for each state:
texas.public.law
, newyork.public.law
, etc.
And there’s one “organizational home site”:
…with pages like About and Contact Us. I want to be able to write request specs like these:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
describe 'robots.txt' do | |
it 'is available on Texas' do | |
get_path '/robots.txt', at_host: 'texas.public.law' | |
expect(response.status).to be 200 | |
end | |
it 'is available on www' do | |
get_path '/robots.txt', at_host: 'www.public.law' | |
expect(response.status).to be 200 | |
end | |
end |
…and feature specs like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
scenario 'www home page has an About link in the nav bar' do | |
visit_path '/', at_host: 'www.public.law' | |
Capybara.within('#top-navbar') do | |
expect(Capybara.page).to have_content('About us') | |
end | |
end |
Spec helpers: get_path and visit_path
I’ve written two helpers, seen in the examples above: get_path
for request specs, and visit_path
for feature specs. get_path
turned out to need very little code. However, it was difficult to figure out because it required reaching through RSpec, the Rails “integration” test support (to get the headers:
parameter) and Rails APIs to determine that host:
is the correct header to supply:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Simplify retrieving a path at a named host in request specs | |
def get_path(path, at_host:) | |
get path, headers: { host: at_host } | |
end |
The equivalent code for feature specs, which are based on Capybara, requires a bit more work:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Perform a Capybara.visit to the given hostname. | |
def visit_path(path, at_host:) | |
MyRailsHelper.set_capybara_host(to_domain: at_host) | |
Capybara.visit path | |
end | |
module MyRailsHelper | |
def self.set_capybara_host(to_domain:) | |
Capybara.current_session.driver.reset! | |
Capybara.app_host = "http://#{to_domain}" | |
Capybara.always_include_port = true | |
end | |
end |
I simply place these helpers in my rails_helper.rb
file.