rspec-rails: UrlGenerationError when testing mocked controllers that use `url_for` just to add params
What Ruby, Rails and RSpec versions are you using?
Ruby version: 2.6.7 Rails version: 6.0.3.7 RSpec version:
RSpec 3.10
- rspec-core 3.10.1
- rspec-expectations 3.10.1
- rspec-mocks 3.10.2
- rspec-rails 5.0.1 (and 4.0.2 before updating)
- rspec-support 3.10.2
Observed behaviour
Using redirect_to url_for(my_extra_param: 'a value') in a Concern, and then testing that concern with a controller test using controller(ActionController::Base.include(MyConcern)) and requesting an action defined in the test controller and drawn with routes.draw, always fails with ActionController::UrlGenerationError as it cannot find the test routes.
Expected behaviour
Test routes added with routes.draw are available and url_for succeeds to build a url of the current route.
Can you provide an example app?
EDIT: here’s a repo that reproduces this issue: https://github.com/henrahmagix/rails-bug-app/tree/c887222dc542572c4453a2c0f987afeefd730065
I will leave the example originally posted here intact below.
Unfortunately rubygems is down right now, so I can’t bundle install on a new app to double-check my setup shows the bug correctly. So here’s the test file that I would add to rails new that shows it:
Click to expand
require 'rspec/rails'
RSpec.configure do |config|
# rspec-expectations config goes here. You can use an alternate
# assertion/expectation library such as wrong or the stdlib/minitest
# assertions if you prefer.
config.expect_with :rspec
config.mock_with :rspec
config.filter_rails_from_backtrace!
end
module MockConcern
extend ActiveSupport::Concern
included do
before_action :redirect_if_requested
end
private
def redirect_if_requested
redirect_to url_for(redirected_from_concern: true) if params[:redirect_me] == 'yes'
end
end
describe 'url_for bug in concern tests', type: :controller do
controller(ActionController::Base.include(MockConcern)) do
def test
render plain: 'testing'
end
end
before do
routes.draw do
get "test" => "anonymous#test"
end
end
it 'succeeds when url_for is not called in the concern' do
get :test, params: { redirect_me: 'no' }, session: nil
expect(response).not_to be_redirect
end
it 'fails when url_for is called in the concern' do
get :test, params: { redirect_me: 'yes' }, session: nil
expect(response).to be_redirect
end
end
class MockController < ActionController::Base
def in_real
redirect_to url_for(extra_param: 'in_real')
end
end
describe 'url_for bug in controller tests', type: :controller do
controller(MockController) do
def test
render plain: 'testing'
end
def in_mock
redirect_to url_for(extra_param: 'in_mock')
end
end
before do
routes.draw do
get "test" => "mock#test"
get "in_real" => "mock#in_real"
get "in_mock" => "mock#in_mock"
end
end
it 'succeeds when url_for is not called' do
get :test, params: nil, session: nil
expect(response).not_to be_redirect
expect(response.body).to eql('testing')
end
it 'fails when url_for is called in the real controller' do
get :in_real, params: nil, session: nil
expect(response).to be_redirect
end
it 'fails when url_for is called in the mock controller' do
get :in_mock, params: nil, session: nil
expect(response).to be_redirect
end
end
I will link to a rails new app that I’ve confirmed shows the same results once rubygems is back up =)
About this issue
- Original URL
- State: open
- Created 3 years ago
- Comments: 15 (6 by maintainers)
Nice find. The last example seems to be affected by the fact that we draw the default routes in
before, and the order of hook execution comes into play. Ifgetis executed before we draw, it fails. Seelib/rspec/rails/example/controller_example_group.rb:Yet another interesing thing is how
routes.drawis implemented, see Rails’actionpack/lib/action_dispatch/routing/route_set.rb:You may see that it clears routes on each call. Unless
There’s also this
rspec-rails’s optionconfig.infer_base_class_for_anonymous_controllers:Where “described class” in the failing test is
RedirectIfRequestedConcern, and you should get a:if you don’t change the test to use a quoted class name:
or set
infer_base_class_for_anonymous_controllerstofalse.