Rspec-rails: expect ... route_to spec is failing to recognize route constraints

Created on 20 Sep 2016  路  9Comments  路  Source: rspec/rspec-rails

Steps to reproduce

routes.rb

resources :users, only: :show

get '/:location_code/:id', to: 'location#details',
    as: :location_details,
    constraints: lambda { |request| Location.find_by_code(request.params[:location_code]) }

routing/users_controller_spec.rb

it 'routes to #show' do
  expect(get: "/user/1").to route_to("users#show", id: '1')
end

* ERROR *
The recognized options <{"controller"=>"users", "action"=>"show", "location_code"=>"users", "id"=>"1"}> did not match <{"id"=>"1", "controller"=>"users", "action"=>"show"}>, difference:. --- expected +++ actual @@ -1 +1 @@ -{"id"=>"1", "controller"=>"users", "action"=>"show"} +{"controller"=>"users", "action"=>"show", "location_code"=>"users", "id"=>"1"}

Expected behavior

Test should pass. expect(get: "/user/1").to route_to("users#show", id: '1') should resolve properly,

Actual behavior

expect ... route_to ... ignores constraints on location_details and saves additional "user" part of the user#show route as location_code param

System configuration

Rails version: 5.0.0.1
Ruby version: 2.3.1
Rspec version: rspec-rails-3.5.2

Hacktoberfest Has reproduction case

Most helpful comment

Hi!
It seems to me that there is nothing wrong with RSpec in this example.
The constraint isn't configured right. If you change:

get '/:location_code/:id', to: 'locations#details',
    as: :location_details,
    constraints: lambda { |request| Location.find_by_code(request.params[:location_code]) }

to:

get '/:location_code/:id', to: 'locations#details',
    as: :location_details,
    constraints: { 
      location_code: lambda { |request| 
        Location.find_by_code(request.params[:location_code]) 
      } 
    }

the test passes.

Why does it work after the change? Well in the first example, though a constraint is defined, it isn't specified to which param it belongs. When you specify that the :location_code has to be a value that the lambda returns (second example) then everything works as expected.

@marcinpopielarz does this solve your issue?

All 9 comments

Hi @marcinpopielarz, thanks for providing steps to reproduce. Would you mind following the guide below to turn it in to a repository we can clone?

Thanks for the issue. We need a little more detail to be able to reproduce this.

Could you please provide us with a rails app that we can clone that demonstrates the issue. Specifically it'd be great if

1) you could rails new an application and commit
2) make all the changes necessary to reproduce the issue and commit

then, provide us with a description of how to clone your application and reproduce the issue.

Thanks :)

To Reproduce

  1. install ruby 2.3.1
  2. git clone https://github.com/marcinpopielarz/rspec_issue
  3. bundle install
  4. bundle exec rspec

You should see

Failures:

  1) UsersController routing routes to #show
     Failure/Error: expect(get: "/users/1").to route_to("users#show", id: '1')

       The recognized options <{"controller"=>"locations", "action"=>"details", "location_code"=>"users", "id"=>"1"}> did not match <{"id"=>"1", "controller"=>"users", "action"=>"show"}>, difference:.
       --- expected
       +++ actual
       @@ -1 +1 @@
       -{"id"=>"1", "controller"=>"users", "action"=>"show"}
       +{"controller"=>"locations", "action"=>"details", "location_code"=>"users", "id"=>"1"}
     # ./spec/routing/users_controller_spec.rb:8:in `block (3 levels) in <top (required)>'

Finished in 0.0453 seconds (files took 3.66 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/routing/users_controller_spec.rb:7 # UsersController routing routes to #show

Notes

  • try running rails s and access http://localhost:3000/users/1 - it works
  • try running rails s and accessing http://localhost:3000/only_single_valid_code/1 - it works
  • same test was working with Rails 4.2.4. I discovered it when migrating to Rails 5
  • when get '/:location_code/:id' is below resources users it passes just fine, so the order is important, but still a) used to work before b) there is no reason to recognize first route if constraint fails. Also it's unexpected to save the parameter from first route, and recognize this parameter in another route.
  • maybe its a Rails issue

expect(get: "/user/1").to route_to("users#show", id: '1', location_code: 'users') should pass, the constraint is checked first and only when it fails does it fallback to users.

Hi!
It seems to me that there is nothing wrong with RSpec in this example.
The constraint isn't configured right. If you change:

get '/:location_code/:id', to: 'locations#details',
    as: :location_details,
    constraints: lambda { |request| Location.find_by_code(request.params[:location_code]) }

to:

get '/:location_code/:id', to: 'locations#details',
    as: :location_details,
    constraints: { 
      location_code: lambda { |request| 
        Location.find_by_code(request.params[:location_code]) 
      } 
    }

the test passes.

Why does it work after the change? Well in the first example, though a constraint is defined, it isn't specified to which param it belongs. When you specify that the :location_code has to be a value that the lambda returns (second example) then everything works as expected.

@marcinpopielarz does this solve your issue?

@Stankec @marcinpopielarz should we close this issue?

Yes

@Stankec @orlando so I'm not sure I totally follow (I'm also completely unfamiliar with this feature)

The rails documentation states in this section http://guides.rubyonrails.org/routing.html#advanced-constraints that you can pass either a lambda or a class. This comes directly under the constraints key, and not under a param name under the constraints key.

Can you explain why this modification is needed?

@samphippen the given example (in Rails guide) has no match attribute, it's a wildcard and therefore doesn't require a specific attribute. In the example given by @marcinpopielarz he has two attributes in the path (: location_code and :id), and uses a specific attribute with the constraint (: location_code), therefore he needs to specify to which attribute the constraint needs to be applied to.

@Stankec got it.

@marcinpopielarz I'm going to close this pending @Stankec's fix. Let me know if that doesn't work for you and we'll circle back.

Was this page helpful?
0 / 5 - 0 ratings