No route matches in functional/controller test - ruby-on-rails

I have the following controller test using Minitest::Rails and Rails 4. When I run it, I get an error: ActionController::UrlGenerationError: No route matches {:action=>"/", :controller=>"foo"} error, despite it being defined.
The whole point of this is to test methods that are on ApplicationController (FooController exists only in this test and is not a placeholder for the question).
class FooController < ApplicationController
def index
render nothing: true
end
end
class FooControllerTest < ActionController::TestCase
it 'does something' do
with_routing do |set|
set.draw do
root to: 'foo#index', via: :get
end
root_path.must_equal '/' #=> 👍
get(root_path).must_be true #=> No route matches error
end
end
end
There a number of similar questions on StackOverflow and elsewhere, but they all refer to the issue of route segments being left out (e.g. no ID specified on a PUT). This is a simple GET with no params, however.
I get the same result if the route is assembled differently, so I don't think it's the root_path bit doing it (e.g. controller :foo { get 'test/index' => :index }).

I did some search on what information you have provided. I found an issue open in rspec-rails gem. However gem doesn't matter here but fundamentally they said its context problem. When you call with_routing it doesn't executed in correct context, so gives error of No Route matches.
To resolve issue, I tried locally with different solution. Here is what I have tried
require 'rails_helper'
RSpec.describe FooController, type: :controller do
it 'does something' do
Rails.application.routes.draw do
root to: 'foo#index', via: :get
end
expect(get: root_path).to route_to("foo#index")
end
end
In above code, the major problem is it overwrite existing routes. But we can reproduce routes with Rails.application.reload_routes! method.
I hope this helps to you!!
UPDTATE
I tried to understand your last comment and dig into get method. When we call get method it takes argument of action of controller for which we are doing test. In our case when we do get(root_path) it tries to find foo#/ which is not exists and hence gives no route matches error.
as our main goal is to check root_path routes are generated correctly, we need to use method assert_routing to check it. Here is how I test and it works
assert_routing root_path , controller: "foo", action: "index"
Full code :
require 'test_helper'
class FooControllerTest < ActionController::TestCase
it 'does something' do
with_routing do |set|
set.draw do
root to: 'foo#index', via: :get
end
root_path.must_equal '/' #=> true
assert_routing root_path , controller: "foo", action: "index" #=> true
get :index
response.body.must_equal ""
end
end
end
I read things from official document : http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html

if you check the source of ActionController::TestCase#get method, it expects action name, e.g. :index, :create, 'edit, 'create'
if you pass root_path on #get method, absolutely it will raise error, because root_path method returns '/'.
I just checked to add :/ method to FooController
class FooController
def index
end
def /
end
end
Rails.application.routes.draw do
root 'foo#index'
get 'foobar' => 'foo#/'
end
when I was visiting http://localhost:3000/foobar, rails gave me
AbstractController::ActionNotFound (The action '/' could not be found for FooController): respond
I think '/' is not permitted action on rails, I don't do research further, because I think it's very reasonable.
You may write
assert_routing '/', controller: "foo", action: "index"
for current test, then you can write integration test to check root_path and other features.
Following are the source code of some methods I've talked above: (I'm using rails version 4.2.3 to test this interesting issue)
action_controller/test_case.rb
# Simulate a GET request with the given parameters.
#
# - +action+: The controller action to call.
# - +parameters+: The HTTP parameters that you want to pass. This may
# be +nil+, a hash, or a string that is appropriately encoded
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
# - +session+: A hash of parameters to store in the session. This may be +nil+.
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
#
# You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with
# +post+, +patch+, +put+, +delete+, and +head+.
#
# Note that the request method is not verified. The different methods are
# available to make the tests more expressive.
def get(action, *args)
process(action, "GET", *args)
end
# Simulate a HTTP request to +action+ by specifying request method,
# parameters and set/volley the response.
#
# - +action+: The controller action to call.
# - +http_method+: Request method used to send the http request. Possible values
# are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+.
# - +parameters+: The HTTP parameters. This may be +nil+, a hash, or a
# string that is appropriately encoded (+application/x-www-form-urlencoded+
# or +multipart/form-data+).
# - +session+: A hash of parameters to store in the session. This may be +nil+.
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
#
# Example calling +create+ action and sending two params:
#
# process :create, 'POST', user: { name: 'Gaurish Sharma', email: 'user#example.com' }
#
# Example sending parameters, +nil+ session and setting a flash message:
#
# process :view, 'GET', { id: 7 }, nil, { notice: 'This is flash message' }
#
# To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+ and +HEAD+ requests
# prefer using #get, #post, #patch, #put, #delete and #head methods
# respectively which will make tests more expressive.
#
# Note that the request method is not verified.
def process(action, http_method = 'GET', *args)
# .....
end

The whole point of this is to test behavior inherited by the ApplicationController.
There is no behavior in your question related to ApplicationController other than the fact that FooController inherits from ApplicationController. And since there's no other behavior to test here in FooController related to something from the Application Controller ...
You can test this
class FooController < ApplicationController
def index
render nothing: true
end
end
with this
describe FooController, type: :controller do
describe '#index' do
it 'does not render a template' do
get :index
expect(response).to render_template(nil)
end
end
end

The solution to this problem was much simpler than expected:
# change this:
get(root_path)
# to this
get(:index)
The with_routing method works fine to define the path in this context.

Related

Unable to test controller action in rspec

I am trying to test a controller action on a non-restful route.
config/routes.rb:
match '/integration/:provider/callback' => "providers#manual_auth", as: :integration_callback
You can see that also via rake routes:
integration_callback /integration/:provider/callback(.:format) providers#manual_auth
In my spec file:
spec/controllers/providers_controller_spec.rb:
describe ProvidersController do
describe '#manual_auth' do
it 'hits the manual_auth action' do
get :manual_auth, use_route: :integration_callback
end
end
end
That gives me an error of:
Failures:
1) ProvidersController#manual_auth hits the manual_auth action
Failure/Error: get :manual_auth, use_route: :integration_callback
ActionController::RoutingError:
No route matches {:controller=>"providers", :action=>"manual_auth"}
However in app/controllers/providers_controller.rb I have
class ProvidersController < ApplicationController
def manual_auth
logger.info "Got into manual auth"
end
end
I should mention I'm purposely avoiding a request spec here because I need to be able to access and set a session object(that lives in this #manual_auth action) which apparently can only be done in controller tests, not request specs.
The integration_callback has one parameter, which is :provider.
Try this:
get :manual_auth, provider: 'test', use_route: :integration_callback

Pass Form Vars in Rspec to Update Method

I'm still fairly new to the rspec way. I've read many posts on a redirect_to but cannot seem to get passed this error. I'm trying to simulate the passing of form variable/value in my rspec test, but getting a routing error. I'm converting tests to rspec tests on an app that is live and working.
In my employees_micros_controller.rb, I have an update method that expects params[:employees_micro][:id]. My question is How do I simulate this in my rspec test?
Controller:
def update
#em = nil
# check for id from form
if params[:employees_micro][:id]
#em = EmployeesMicro.find_by_id(params[:employees_micro][:id])
end
....
end
Rspec Test: note: ### error line ###
# update and redirect
describe 'POST #update' do
it "updates employee_micro and redirect to employee_details" do
# #emp_micros set in before(each) above
#emp_micros.home_job = 123
# update method looks for params[:employees_micro][:id]
post :update, :employees_micro => { :id => #emp_micros } ### error line ###
expect(response).to redirect_to("employee_details_employee_path")
end
end
Error:
Failure/Error: post :update, :employees_micro => { :id => #emp_micros }
ActionController::RoutingError:
No route matches {:employees_micro=>{:id=>"11960"}, :controller=>"employees_micros", :action=>"update"}
Is my >> post :update line syntax correct?
I do not understand the routing error. I'm only trying to simulate passing a form variable to the update method from my test. If I remove everything after the post/put line, I still get the same error, so it is definitely on that line.
I've also tried using "put" in place of "post" in the test. It nets me the same error.
Thanks for any tips or advice.
The update route expects the :id param to be at the top level, so use:
post :update, :id => #emp_micros, :employees_micro => { <other attributes to update> }

Using rails' "post" in controller tests in rspec with scoping and protocol on routes

I have a rails project that is running out of a subdirectory of the base url in production and I want it to act that way in dev so as to make dev and prod as close as possible. I have the routes file set up like so:
Foo::Application.routes.draw do
def draw_routes
root :to=>'foo#home'
resources :cats, :only=>[:create] do
end
if Rails.env.production?
scope :protocol=>'http://' do
draw_routes
end
else
scope :path=>"/foo", :protocol=>'http://' do
draw_routes
end
end
end
My CatsController is like this:
class CatsController < ApplicationController
def create
#cat = Cat.new(:name=>"Bob")
if #cat.save()
redirect_to root
end
end
end
I want to test my Create Cat Method, so I set up an rspec test in spec/controllers/cats_controller_spec.rb:
require 'spec_helper'
describe CatsController do
describe "calling create cat with good data" do
it "should add a cat" do
expect {post(:action=>:create)}.to change(Cat, :count).by(1)
end
end
end
When I run my test, though, I get
Failure/Error: post :create
ActionController::RoutingError:
No route matches {:controller=>"cats", :action=>"create"}
# ./spec/controllers/cats_controller_spec.rb:5:in `(root)'
Rake routes says my route is there! What is going on under the hood here, and why is this failing?
Rake Routes:
cats POST /foo/cats(.:format) cats#create {:protocol=>"http://"}
The problem is that the value of :protocol is a bit wonky.
The better way to fix this, I think, is to set the protocol in your scopes to http instead of http://. If you did need to test your controller with some funky protocol, though, you should be able to do:
post(:action => :create, :protocol => 'funky')
or whatever your protocol might be.
For rspec 3, this below works for me.
post :action, protocol: 'https://'

In Rails Controller testing, is there a way to pass query (non-routing) parameters?

I'm writing controller tests in Rails and RSpec, and it seems from reading the source code of ActionController::TestCase that it's not possible to pass arbitrary query parameters to the controller -- only routing parameters.
To work around this limitation, I am currently using with_routing:
with_routing do |routes|
# this nonsense is necessary because
# Rails controller testing does not
# pass on query params, only routing params
routes.draw do
get '/users/confirmation/:confirmation_token' => 'user_confirmations#show'
root :to => 'root#index'
end
get :show, 'confirmation_token' => CONFIRMATION_TOKEN
end
As you may be able to guess, I am testing a custom Confirmations controller for Devise. This means I am jacking into an existing API and do not have the option to change how the real mapping in config/routes.rb is done.
Is there a neater way to do this? A supported way for get to pass query parameters?
EDIT: There is something else going on. I created a minimal example in https://github.com/clacke/so_13866283 :
spec/controllers/receive_query_param_controller_spec.rb
describe ReceiveQueryParamController do
describe '#please' do
it 'receives query param, sets #my_param' do
get :please, :my_param => 'test_value'
assigns(:my_param).should eq 'test_value'
end
end
end
app/controllers/receive_query_param_controller.rb
class ReceiveQueryParamController < ApplicationController
def please
#my_param = params[:my_param]
end
end
config/routes.rb
So13866283::Application.routes.draw do
get '/receive_query_param/please' => 'receive_query_param#please'
end
This test passes, so I suppose it is Devise that does something funky with the routing.
EDIT:
Pinned down where in Devise routes are defined, and updated my example app to match it.
So13866283::Application.routes.draw do
resource :receive_query_param, :only => [:show],
:controller => "receive_query_param"
end
... and spec and controller updated accordingly to use #show. The test still passes, i.e. params[:my_param] is populated by get :show, :my_param => 'blah'. So, still a mystery why this does not happen in my real app.
Controller tests don't route. You are unit-testing the controller--routing is outside its scope.
A typical controller spec example tests an action:
describe MyController do
it "is successful" do
get :index
response.status.should == 200
end
end
You set up the test context by passing parameters to get, e.g.:
get :show, :id => 1
You can pass query parameters in that hash.
If you do want to test routing, you can write routing specs, or request (integration) specs.
Are you sure there isn't something else going on? I have a Rails 3.0.x project and am passing parameters.. well.. this is a post.. maybe it's different for get, but that seems odd..
before { post :contact_us, :contact_us => {:email => 'joe#example.com',
:category => 'Category', :subject => 'Subject', :message => 'Message'} }
The above is definitely being used in my controller in the params object.
I am doing this now:
#request.env['QUERY_STRING'] = "confirmation_token=" # otherwise it's ignored
get :show, :confirmation_token => CONFIRMATION_TOKEN
... but it looks hacky.
If someone could show me a neat and official way to do this, I would be delighted. Judging from what I've seen in the source code of #get and everything it calls, there doesn't seem to be any other way, but I'm hoping I overlooked something.

How do I test that a before_filter will redirect for all Rails controller actions?

I have a fairly typical require_no_user as a before_filter in one of my controllers. I need to test that a logged in user is redirected by this filter if they try to access any of the controller's actions.
Is there a sensible way to do this without enumerating all of the controller's actions in my test case?
I'm trying to avoid:
context 'An authenticated user' do
setup do
activate_authlogic
#user = Factory(:user)
UserSession.create(#user)
do
should 'not be allowed to GET :new' do
get :new
assert_redirected_to(root_path)
end
should 'not be allowed to POST :create' do
# same as above
end
# Repeat for every controller action
end
Not that I'm aware of... though you could make it a bit shorter by packing all the methods and actions into a hash:
should "be redirected" do
{
:get => :new,
:post => :create,
}.each do |method, action|
send(method, action)
assert_redirected_to(root_path)
end
end
Edit: so yeah, this is probably overkill, but here's another way:
should "be redirected" do
ActionController::Routing::Routes.named_routes.routes.each do |name, route|
if route.requirements[:controller] == #controller.controller_name
send(route.conditions[:method], route.requirements[:action])
assert_redirected_to(root_path)
end
end
end
Seems though that if you define multiple :methods in custom routes that it still only "finds" the first, e.g.
map.resources :foo, :collection => {
:bar => [:get, :post]
}
The above route will only be attempted with the GET verb.
Also if there are other requirements in the URL, such as presence of a record ID, my naive example ignores that requirement. I leave that up to you to hack out :)

Resources