RSpec incorrectly adds id parameter in index routing test - ruby-on-rails

I have a /bar/foos path that routes to FoosController. GETting /bar/foos routes to FoosController#index, which works as expected when testing manually.
I inherited some routing tests for this which worked fine with Rails 4.2, but started breaking when updating to Rails 5.0.
For some reason RSpec is creating a id: :foos parameter in expect(get: '/bar/foos') in this spec:
require 'spec_helper'
describe FoosController do
describe 'routing' do
it 'get /bar/foos' do
expect(get: '/bar/foos').to route_to('foos#index')
end
end
end
Which yields this error:
The recognized options <{"controller"=>"foos", "action"=>"index", "id"=>"foos"}>
did not match <{"controller"=>"foos", "action"=>"index"}>, difference:.
--- expected
+++ actual
## -1 +1 ##
-{"controller"=>"foos", "action"=>"index"}
+{"controller"=>"foos", "action"=>"index", "id"=>"foos"}
I am using Rails 5.0.2 and rspec 3.6.0.beta2.

Related

Rails integration test don't see routes

I am writing because I recently added a two-step authorization to active_admin in my application using the gem: 'devise-two-factor'. Everything is working, unfortunately when I started to write tests I noticed that the paths I added do not exist in the test environment (there is an error). What is the reason for this and how to fix it. thanks in advance for your help.
Error:
test_can_setup_2FA_for_admin_user ERROR (325.99s)
Minitest::UnexpectedError: ActionController::RoutingError: No route matches [GET] "/admin/admin_users/6/edit_two_factor"
test/controllers/admin/sessions_controller_test.rb:11:in `block in <class:AdminUsersControllerTest>'
Finished in 325.99535s
1 tests, 0 assertions, 0 failures, 1 errors, 0 skips
[SlowTest] AdminUsersControllerTest#test_can_setup_2FA_for_admin_user : 325.99s
routes:
rails routes | grep edit_two_factor
edit_two_factor_authentication_confirmation GET /two_factor_authentication/confirmation/edit(.:format) two_factor_authentication/confirmations#edit {:host=>"571e-77-222-242-230.eu.ngrok.io"}
edit_two_factor_authentication_recovery_code GET /two_factor_authentication/recovery_codes/:id/edit(.:format) two_factor_authentication/recovery_codes#edit {:host=>"571e-77-222-242-230.eu.ngrok.io"}
edit_two_factor_authentication GET /two_factor_authentication/edit(.:format) two_factor_authentications#edit {:host=>"571e-77-222-242-230.eu.ngrok.io"}
edit_two_factor_admin_admin_user GET /admin/admin_users/:id/edit_two_factor(.:format) admin/admin_users#edit_two_factor {:host=>"571e-77-222-242-230.eu.ngrok.io"}
test code:
class AdminUsersControllerTest < ActionDispatch::IntegrationTest
test 'can setup 2FA for admin user' do
admin_user = create(:admin_user)
get edit_two_factor_admin_admin_user_path(admin_user)
assert_template 'admin/admin_users/show_two_factor'
end
end
RAILS_ENV=test rails routes | grep edit_two_factor
edit_two_factor_authentication_confirmation GET /two_factor_authentication/confirmation/edit(.:format) two_factor_authentication/confirmations#edit {:host=>"prograils.io"}
edit_two_factor_authentication_recovery_code GET /two_factor_authentication/recovery_codes/:id/edit(.:format) two_factor_authentication/recovery_codes#edit {:host=>"prograils.io"}
edit_two_factor_authentication GET /two_factor_authentication/edit(.:format) two_factor_authentications#edit {:host=>"prograils.io"}
edit_two_factor_admin_admin_user GET /admin/admin_users/:id/edit_two_factor(.:format) admin/admin_users#edit_two_factor {:host=>"prograils.io"}

Rails 5 Integration test fails with NoMethodError: undefined method `[]=' for nil:NilClass when using Devise helper sign_in

I'm writing an integration test for Rails v5.1 using built-in Minitest.
Here's the integration test class:
require 'test_helper'
class PuppiesEndpointsTest < ActionDispatch::IntegrationTest
include Devise::Test::IntegrationHelpers
test "DELETE puppy" do
marty = people(:marty)
sign_in(marty)
# delete puppies_delete_path(marty.puppies.first.id)
# delete `/api/v1/puppies/destroy/${marty.puppies.first.id}.json`
# delete puppies_path(marty.puppies.first.id)
delete '/api/v1/puppies/destroy/6666.json'
assert_response :success
end
end
All of the routes above, including the ones that are commented out, result in the same cryptic error:
Error:
PuppiesEndpointsTest#test_DELETE_puppy:
NoMethodError: undefined method `[]=' for nil:NilClass
test/integration/puppies_endpoints_test.rb:17:in `block in <class:PuppiesEndpointsTest>'
bin/rails test test/integration/puppies_endpoints_test.rb:7
It doesn't give a stack trace or any other information to diagnose what the hell it's talking about. I used byebug to debug the marty variable right before that delete line that's throwing the error. It shows the expected puppies array of associated (fixture) records.
I've also placed a byebug at the very top of the controller action and this error fails the test before it reaches that byebug, so I think that pretty much rules out anything in the action code.
Here's the relevant chunk of what I see when I run rake routes:
PATCH /api/v1/puppies/edit/:id(.:format) puppies#update
DELETE /api/v1/puppies/destroy/:id(.:format) puppies#destroy
puppies_create POST /api/v1/puppies/create(.:format) puppies#create
Here's what is actually in the my routes file:
scope '/api' do
scope '/v1' do
devise_for :people
patch 'puppies/edit/:id' => 'puppies#update'
delete 'puppies/destroy/:id' => 'puppies#destroy'#, as: 'puppies_delete'
post 'puppies/create' => 'puppies#create'
...
I'm completely stumped as to what/why I'm getting this error. The actual code is working completely as expected.
My hunch is that maybe there's a missing config variable that's not getting set for the test environment (I use dotenv gem), but I have no idea how to track that down if the error won't give me any context whatsoever.
UPDATE
I have isolated this problem to using the Devise helper sign_in method. When I remove this method call, the problem goes away.
Here's the problematic test class:
require 'test_helper'
class PuppiesEndpointsTest < ActionDispatch::IntegrationTest
include Devise::Test::IntegrationHelpers
test "do stuff" do
...
app/controllers/api_controller.rb:
class ApiController < ActionController::API
end
Maybe sign_in does not work for testing controllers that do not inherit from ActionController::Base
I changed the controller to inherit from ActionController::Base and nothing changed. I still can't use sign_in without getting that error, but it works find if I "manually" post a request to the sign_in endpoint.
UPDATE 2
I found this Devise issue which sounds related to my problem: https://github.com/plataformatec/devise/issues/2065
Looks like I found the issue. Apparently, in rails-api mode, the ActionDispatch::Cookies and ActionDispatch::Session::CookieStore middlewares are inserted in the end of the middleware stack, which doesn't occur in normal Rails mode.
Due to this, those middlewares are included after Warden::Manager which messes up something in request specs.
Try to set in test.rb
Rails.application.config.middleware.insert_before Warden::Manager, ActionDispatch::Cookies
Rails.application.config.middleware.insert_before Warden::Manager, ActionDispatch::Session::CookieStore

Why does my rspec-rails generated spec fail due to a routing exception?

I generated a scaffold with Rails (4.1.16) and Rspec (3.5.1).
It generated this test:
describe "GET #show" do
it "assigns the requested team as #team" do
team = Team.create! valid_attributes
get :show, params: {id: team.to_param}, session: valid_session
expect(assigns(:team)).to eq(team)
end
end
Which outputs this error:
TeamsController GET #show assigns the requested team as #team
Failure/Error: get :show, params: {id: team.to_param}, session: valid_session
ActionController::UrlGenerationError:
No route matches {:action=>"show", :controller=>"teams", :params=>{:id=>"82"}, :session=>{}}
If I remove the keys to the parameters to get, i.e.:
get :show, {id: team.to_param}, valid_session
The test passes fine.
Not sure what gem defines the generator template (rspec-rails?) and why I get this error. Help would be appreciated understanding this issue. Thanks.
The generator (rspec:scaffold, which comes with rspec-rails) is generating tests with the syntax required by Rails 5 (see the last section of that blog post), which is not compatible with Rails 4. I think this is a bug in rspec-rails, since rspec-rails 3.5 is otherwise compatible with Rails 4. (I'm using those versions together myself; I just haven't used the generator.)
rspec-rails was changed to use the Rails 5 syntax in rspec-rails 3.5.0.beta4, so one workaround is to use rspec and rspec-rails 3.4 — not so nice since the newer versions have features and fixes which are as useful with Rails 4 as with Rails 5. Another workaround is to manually fix the output of the generator as you did.

Routing specs fails because hashes are stored differently

My test example is:
it "routes to #add_role" do
post("/users/1/add_role").should route_to("users#add_role",id: 1)
end
Here is the failure message:
UsersController routing routes to #add_role
Failure/Error: post("/users/1/add_role").should route_to("users#add_role",id: 1)
The recognized options <{"controller"=>"users", "action"=>"add_role", "id"=>"1"}> did not match <{"id"=>1, "controller"=>"users", "action"=>"add_role"}>, difference: <{"id"=>1}>.
<{"id"=>1, "controller"=>"users", "action"=>"add_role"}> expected but was
<{"controller"=>"users", "action"=>"add_role", "id"=>"1"}>.
My environment:
ruby-1.9.3-p194
Rails 3.2.6
Rspec (2.10.0)
You need id in the route_to to be string.
Rails does not type cast into numbers as it makes no assumptions about types

Do routing specs support redirect routes? [RSpec]

After digging fairly deeply on this issue, I've come to an impasse between my understanding of the documentation and my results.
According to https://www.relishapp.com/rspec/rspec-rails/v/2-8/docs/routing-specs/route-to-matcher, we should be able to write the following:
#rspec-rails (2.8.1)
#rspec (>= 1.3.1)
#rspec-core (~> 2.8.0)
# routing spec
require "spec_helper"
describe BusinessUsersController do
describe "routing" do
it "routes to some external url" do
get("/business_users/7/external_url").should route_to("http://www.google.com")
end
end
end
# routes.rb
BizeebeeBilling::Application.routes.draw do
resources :business_users do
member do
get "external_url" => redirect("http://www.google.com")
end
end
end
Running this spec produces the following results:
Failures:
1) BusinessUsersController routing routes to some external url
Failure/Error: assert_routing "/business_users/7/external_url", "http://www.google.com"
ActionController::RoutingError:
No route matches "/business_users/7/external_url"
# ./spec/routing/business_users_routing_spec.rb:19:in `block (3 levels) in <top (required)>'
I have not been able to find anyone reporting this specific issue anywhere.
Added detail: the route is resolved perfectly well when testing manually.
Routing specs/tests specialize in testing whether a route maps to a specific controller and action (and maybe some parameters too).
I dug into the internals of Rails and Journey a bit. RSpec and Rails (basically, some details left out) use Rails.application.routes.recognize_path to answer the question "is this routable?"
For example:
$ rails console
> Rails.application.routes.recognize_path("/business_users/1", method: "GET")
=> {:action=>"show", :controller=>"business_users", :id=>"1"}
However, there's no controller on the other end of /business_users/1/external_url. In fact, to perform the redirect, Rails has created an instance of ActionDispatch::Routing::Redirect, which is a small Rack application. No Rails controller is ever touched. You're basically mounting another Rack application to perform the redirection.
To test the redirect, I recommend using a request spec instead (a file in spec/requests). Something like:
require "spec_helper"
describe "external redirection" do
it "redirects to google.com" do
get "/business_users/1/external_url"
response.should redirect_to("http://www.google.com")
end
end
This tests the route implicitly, and allows you to test against the redirection.
Andy Lindeman has the correct answer. However, you don't have to put the spec in spec/requests, you can keep it in spec/routing and be explicit with the metadata "type": describe 'my route', type: :request do
I was running into a similar case where I was trying to test a series of routes, some which should redirect and some which shouldn't. I wanted to keep them in a single routing spec, since that was the most logical way to group them.
I tried using describe: 'my route', type: request, but found that not to work. However, you can include RSpec::Rails::RequestExampleGroup in your spec context to gain access to the request spec methods. Something like:
describe "My Routes" do
context "Don't Redirect" do
it "gets URL that doesn't redirect" do
get("business_users/internal_url").should route_to(controller: "business_users", action: "internal_url_action")
end
end
context "Redirection" do
include RSpec::Rails::RequestExampleGroup
it "redirects to google.com" do
get "/business_users/1/external_url"
response.should redirect_to("http://www.google.com")
end
end
end
The simplest way to test external redirects is to use an integration test:
test "GET /my_page redirects Google" do
get "/my_page"
assert_redirected_to "https://google.com"
end
You test needs to be under your test/integration directory or the equivalent directory where the integration tests should go.
I think you want the redirect_to matcher.

Resources