Using default_url_options in RSpec with Rails 5

There's a long-standing bug in the integration of controller testing into RSpec that prevents you from easily setting default_url_options for your controller specs. As far as I can tell, it doesn't get fixed because the RSpec teams considers the problem a bug in Rails, and the Rails team does not care if RSpec breaks.

I'm talking about the issue you run into when you're trying to work with locale settings passed into your application as namespace variables in routes.rb like this:

scope "/:locale" do
    devise_for :users,  #...and so on

Today I learned that the inability to set a default :locale parameter can be maddening. Your specs will fail with ActionView::Template::Error: No route matches errors:

1) Devise::RegistrationsController POST /users should allow registration
     Failure/Error: %p= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token)

     ActionView::Template::Error:
       No route matches {"action":"show","confirmation_token":"pcyw_izS8GchnT-R3EGz","controller":"devise/confirmations"} missing required keys: [:locale]

The reason is that ActionController::TestCase ignores normal settings of default_url_options in ApplicationController or your config/environments/test.rb. No other intuitive attempt at a workaround worked either. Frustratingly, it took me around an hour to debug and come up with a monkeypatch-style workaround. The existing workarounds that I could find online are all broken in Rails 5.

So here it is:

# spec/support/fix_locales.rb
ActionController::TestCase::Behavior.module_eval do
  alias_method :process_old, :process

  def process(action, *args)
    if params = args.first[:params]
      params["locale"] = I18n.default_locale
    end
    process_old(action, *args)
  end
end

Note the assumption that you are passing params in your spec using a symbol key and not the string "params".