I have an RSpec controller spec and I'm trying to understand how to find what exact route is being called in my example.
In services_controller_spec.rb:
describe 'create comment' do
let!(:service) { FactoryGirl.create(:service) }
describe 'with valid comment' do
it 'creates a new comment' do
expect {
post :add_comment, id: service.id
}.to change(service.service_comments, :count).by(1)
expect(response).to redirect_to(service_path(service))
end
end
end
Is there a way to pp or puts the route that is being sent via the post?
I am asking because I want to post to the route /services/:id/add_comment and want to verify where exactly the route is going.
My routes.rb for this route:
resources :services do
member do
post 'add_comment'
end
end
You can print the name of the route used in an rspec-rails controller spec with something like this:
routes.formatter.send(
:match_route,
nil,
controller: ServicesController.controller_path,
action: 'add_comment', # what you passed to the get method, but a string, not a symbol
id: service.id # the other options that you passed to the get method
) { |route| puts route.name }
rspec-rails uses the route only internally. The above is how rspec-rails (actually ActionController::TestCase, which rspec-rails uses) looks up and uses the route, but with a block that just prints the route.
There are a lot of method calls between the post call in a spec and the above, so if you want to understand how rspec-rails gets to the above I suggest putting a breakpoint in ActionDispatch::Journey::Formatter.match_routes before running your example.
Note that an rspec-rails controller spec doesn't use the route to decide what action method to call or what controller class to call it on; it already knows them from the controller class you pass to describe and the action you pass to the action method (get, post, etc.). However, it does look up the route and use it to format the request environment. Among other uses, it puts the path in request.env['PATH_INFO'].
I investigated this in Rails 4.1, since that's what the project I have handy uses. It might or might not be accurate for other versions of Rails.
Related
I have a route that looks like this in my Rails app:
Rails.application.routes.draw do
scope module: "api" do
namespace :v1 do
# snip
post "my_route", to: "my_controller#my_action"
I'm trying to write a controller test for this action:
RSpec.describe Api::V1::MyController, type: :controller do
describe "POST my_route" do
it "should respond with a 200 status" do
post "api/v1/my_route"
expect(response.status).to eq(200)
end
end
end
When I do this, my test fails with a ActionController::UrlGenerationError error.
What string should I use in my call to post so that RSpec correctly matches my route when it simulates the request? (Notice that my controller lives in the Api::V1 module; I'm not sure whether this makes a difference or not.)
I've tried:
"api/v1/my_route"
"/api/v1/my_route"
"v1/my_route"
"/v1/my_route"
"my_route"
"/my_route"
I get the same error with all of these strings, and I'm not sure what else could possibly be expected.
There are many other questions about UrlGenerationError in RSpec tests. None of them have helped me because they all seem to use built-in Rails routes, like :index or :create. I've specified my action and route directly, so I can't rely on quite as much Rails magic.
I believe that if I knew which format RSpec was expecting me to give for the path string I pass to post, I'd be able to figure this out very quickly. Unfortunately, I've had a hard time finding the relevant docs. It seems like most of RSpec's documentation is based on showing example tests, and again, since I'm not using much Rails magic, their examples don't show me what I'm supposed to be doing. What is the format for the URL string I'm supposed to use? Can you please point me to the relevant docs?
Controller RSpec paths don't expect you to use the routes specified in config/routes.rb when simulating requests. Instead, use the method names on the controllers themselves.
In my example, I have this route:
Rails.application.routes.draw do
scope module: "api" do
namespace :v1 do
# snip
post "my_route", to: "my_controller#my_action"
And this controller:
module Api
module V1
class MyController
def my_action
# snip
So the name of the route is my_route, and the name of the controller method is my_action. In writing the corresponding test case, I need to refer to my_action, the method, not my_route, the route.
Like so:
RSpec.describe Api::V1::MyController, type: :controller do
describe "POST my_route" do
it "should respond with a 200 status" do
post "my_action"
expect(response.status).to eq(200)
end
end
end
(Although I found this unintuitive at first, it does make sense now that I think about it. It decouples the controller tests from the routes configuration, making the tests less brittle.)
I'm using a custom rspec matcher within a controller spec the message is always empty.
The spec looks like:
describe QuestionnaireController do
matcher :redirect_to_sign_in_if_not_authenticated do |method|
match do |controller|
self.send(method)
response.should redirect_to new_user_session_path
end
end
describe "GET index" do
it { should redirect_to_sign_in_if_not_authenticated(get :index) }
end
end
When running this test, and it fails, all that comes up is:
Failures:
1) QuestionnaireController GET show
As you can see the default should message is missing here. How do I get it to show up?
You can use a failure_message_for_should block, as described here: https://www.relishapp.com/rspec/rspec-expectations/v/2-4/docs/custom-matchers/define-matcher#overriding-the-failure-message-for-should
However, you're probably going to run into a few problems here:
get :index will actually call the get method, and then pass the return value to the matcher, which the code does not seem to be expecting.
Errors & backtraces will probably be messed up if you use another matcher (should redirect_to) inside your custom matcher.
You might want to consider a shared example instead: https://www.relishapp.com/rspec/rspec-core/docs/example-groups/shared-examples
I'm trying to DRY up my RSpec examples by adding a few controller macros for frequently used tests. In this somewhat simplified example, I created a macro that simply tests whether getting the page results in a direct to another page:
def it_should_redirect(method, path)
it "#{method} should redirect to #{path}" do
get method
response.should redirect_to(path)
end
end
I'm trying to call it like so:
context "new user" do
it_should_redirect 'cancel', account_path
end
When I run the test I get an error saying that it doesn't recognize account_path:
undefined local variable or method `account_path' for ... (NameError)
I tried including Rails.application.routes.url_helpers per the guidance given in this SO thread on named routes in RSpec but still receive the same error.
How can I pass a named route as a parameter to a controller macro?
The url helpers included with config.include Rails.application.routes.url_helpers are valid only within examples (blocks set with it or specify). Within example group (context or describe) you cannot use it. Try to use symbols and send instead, something like
# macro should be defined as class method, use def self.method instead of def method
def self.it_should_redirect(method, path)
it "#{method} should redirect to #{path}" do
get method
response.should redirect_to(send(path))
end
end
context "new user" do
it_should_redirect 'cancel', :account_path
end
Don't forget to include url_helpers to config.
Or call the macro inside example:
def should_redirect(method, path)
get method
response.should redirect_to(path)
end
it { should_redirect 'cancel', account_path }
I have a check_user_access_control before_filter in my ApplicationController that checks the logged user's roles and permissions before it lets him through. I am trying to write some tests on it and I can't find a good way of doing it.
For simple index actions I simply do:
it "allows access to mod" do
login_as(Factory(:mod)) # this is a spec helper
get :index
response.code.should == "200"
end
and it works just fine. For edit/show/create and other actions that need some params, interactions with the database and possible redirect after they run, it needs too many other stuff to be stubbed.
Is there a way to test if a specific action has been called after the before_filters? I am looking for something like controller.should_receive(:action_name) (which doesn't work) to replace the response.code.should == "200" line with.
versions: rails 3.0.4 and rspec 2.5
I tried another approach. We have a method in ApplicationController called redirect_to_login that I am now checking for with controller.should_receive(:redirect_to_login) and works.
While it detects correctly if the user is allowed or not, it stubs the method, which means that the controller action is run whether or not the user is allowed. Moreover the action depends on params and database and we don't want that.
If now I stub the action method with controller.stub!(:action_name), the action is not run but RSpec is still looking for the template. Well, some actions don't have templates, they just end with a redirect_to :action => :somewhere_else or render :text => "foobar" which at this point we don't care about.
In sort, what I need now is to find a way to make RSpec NOT worry about the template's existence.
When stubbing, you could still give a dummy implementation. Inside that implementation you could then raise an error, to make sure all execution is halted, or you do a redirect anyway.
E.g.
controller.should_receive(:redirect_to_log) { redirect_to login_url }
or
controller.should_receive(:redirect_to_log) { raise StandardError.new('login error') }
expect { get :index }.to raise_error
For more information check out the awesome rspec documentation.
Hope it helps.
To extend #nathanvda's answer:
When stubbing, you could still give a dummy implementation. Inside that implementation [...] do a redirect anyway.
You need to specify controller in the block:
expect(controller).to receive(:redirect_to_log) { controller.redirect_to login_url }
RSpec has a matcher that is also called redirect_to that takes precedence when looking up the method. Calling it directly on the controller works around that.
Final solution, with thanks to nathanvda:
it "allows access to moderator" do
login_as(Factory(:mod))
controller.stub!(action) { raise "HELL" }
controller.should_not_receive(:redirect_to_login)
expect { get action }.to raise_error(/HELL/)
end
it "denies access to user" do
login_as(Factory(:user))
controller.should_receive(:redirect_to_login) { raise "HELL" }
expect { get :index }.to raise_error(/HELL/)
end
posted on https://gist.github.com/957565
I'm getting a failing test here that I'm having trouble understanding. I'm using Test::Unit with Shoulda enhancement. Action in users_controller.rb I'm trying to test...
def create
unless params[:user][:email] =~ / specific regex needed for this app /i
# ...
render :template => 'sessions/new'
end
end
Test...
context 'on CREATE to :user' do
context 'with invalid email' do
setup { post :create, { 'user[email]' => 'abc#abcd' } }
should_respond_with :success
end
# ...
end
Fails because "response to be a <:success>, but was <302>". How is it 302?
Change action to...
def create
render :template => 'sessions/new'
end
Test still fails.
#Ola: You're wrong: POST is connected to create. PUT is normally connected to update.
A :forbidden is quiet odd though. Here are some suggestions to find the problem (I've never used Shoulda, but I don't think it is a problem with Shoulda.
Make sure the route is defined in config/routes.rb and check with rake routes
Do you have any before_filters that could be responsible for that behaviour (login filter, acts_as_authenticated etc..)? Checkout log/test.log. A halt in the filter chain shows up there.
Print out the response body puts response.body to see what you get returned.
Hope this helps.
If you're using default REST-ful URLs, you probably should use PUT, not POST... Since PUT is connected to create, POST to that URL will give you an unauthorized and redirect.