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:
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:
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:
# 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:
# 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.