Testing the multi-subdomain Rails app

Writing tests for a multi-tenantmulti-subdomain app turns out to be very tricky to figure out, e.g.:

Screen Shot 2018-06-08 at 12.49.57 AM

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.lawnewyork.public.law, etc.

And there’s one “organizational home site”:

www.public.law

…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

view raw
get_path.rb
hosted with ❤ by GitHub

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

view raw
visit_path.rb
hosted with ❤ by GitHub

I simply place these helpers in my rails_helper.rb file.