Testing controller's redirect in RSpec - ruby-on-rails

I have a rspec test testing a controller action.
class SalesController < ApplicationController
def create
# This redirect sends the user to SalesController#go_to_home
redirect_to '/go_to_home'
end
def go_to_home
redirect_to '/'
end
end
My controller test looks like
RSpec.describe SalesController, type: :controller do
include PathsHelper
describe 'POST create' do
post :create
expect(response).to redirect_to '/'
end
end
However, when I run the test it tells me that:
Expected response to be a redirect to <http://test.host/> but was a redirect to <http://test.host/go_to_home>.
Expected "http://test.host/" to be === "http://test.host/go_to_home".
/go_to_home will send the user to SalesController#go_to_home. How can I test that the response will eventually lead to the home page with the url http://test.host/?

Why are you expecting to be redirect to '/' in the specs?
From the controller code you pasted you are going to be redirected to '/go_to_home' after hitting the create action
Try changing the specs to:
expect(response).to redirect_to '/go_to_home'
Edit:
Is this a real example or the code is just for sharing what you are trying to achieve?
I don't think rspec will follow the redirect after going to '/go_to_home' and I think is fine.
If you are testing the create action it's ok to test that redirects to '/go_to_home' because that's what action is doing.
Then you can do another test for the other action go_to_home and expect that redirects to root.
Are you calling the action 'go_to_home' from somewhere else?

Controller tests are effectively unit tests - you are testing the effect of calling a single action and what the expected behaviour of that action is.
The create action does return a response back with a status code of 302 and includes in the header a Location indicating the new URI, which in the case of calling create would be Location: http://localhost/go_to_home
This is as far as the controller test goes. It has emulated a call made from a browser to the create action and received the initial redirection.
In the real world of course the browser would then navigate to the given location and would then hit the go_to_home action but this is beyond the scope of controller tests ... this is in the domain of integration testing.
So, either,
Create an integration test to initially call the create action, follow the redirection and test that you end up at '/'.
Change the controller test to expect(response).to redirect_to '/go_to_home'
Change the create action to redirect directly to '/'

Related

Stub controller redirect_to external url with VCR

Some third-party service which I want to use requires user to log in on their webpage. Handy url is generated in controller below. User goes there and come back to my service after his authentication succeeds
class MyController < App::BaseController
def login
redirect_to SOME_API.external_url(ENV['secret_id'])
end
end
As user comes back to my service he brings some feedback from third-party service in URL params (like: myapp.com/login_callack?response=error&reason=wrong_password). There are many variants of these params so I need to handle them in my action
def login_callback
#SOME MAGIC WITH COOKIES/ERRORS/What ever
redirect_to root_path
end
I want to write feature specs for it using RSpec and capybara.
scenario 'User authenticates with third-party thing' do
visit '/anything'
click_link 'Go to MyController#login and than get redirect to somerandom.url/login_logic'
# use cassette below
fill_out_form
click_button confirm
# magic happens on their side and their redirect user to my app into #login_callback action
expect(page).to have(/all fancy things i need/)
end
end
however calling WebMock.disable_net_connect!(allow_localhost: true) or adding vcr: { cassette_name: 'using_third_party_login', record: :new_episodes } doesn't prevent this scenario to being redirect to external url.
Capybara just let being redirected to external uri and no cassets are being recorded/played. Any idea how to stub redirect_to external_url with cassette play/record?

Sending Format with Rspec GET

I'm attempted to test the mobile version of my rails site, but i can't seem to get the following code to work:
let(:uri) { '/' }
it 'routes to #mobile_index' do
get uri, :format => 'mobile'
controller.response.should route_to('home#index_mobile')
end
What's the proper way to send this sort of request so its seen by the app as coming from a mobile source? I've looked up a lot about setting the user agent, but i can't get any of those to work either. I'm using Rspec version 2.14.2.
How do you check if whether to redirect to mobile page or to the normal?
For this testcode to work you must be having something like this in your application#index
respond_to do |format|
format.mobile do
# redirect to mobile
end
format.html
end
This means if you call '/index' (or '/' ) and if you call '/index.mobile' it would be
redirecting to the mobile page
Because you've written something about the User Agent i guess this is your criterium for
distinguishing between mobile and normal version.
HTTP Headers in rails tests are set by the request.env method. Their names are prefixed
with HTTP_, capitalized and have dashes replaced by underscores.
so for setting the User-Agent header you just do
request.env['HTTP_USER_AGENT'] = "WWW-Mechanize"
and then perform the get call.
If you are checking only one and not multiple controllers in integration i would also make this a functional test of the Application Controller (or whatever controller responsible for the home action)
describe ApplicationController do
describe "GET index" do
it "redirects mobile agents to the mobile version" do
request.env['HTTP_USER_AGENT'] = 'Ipod ...'
#calls "/index" unless different routing configured
get :index
expect(response).to redirect_to <index_mobile_path> #path helper depends on route config
end
end
end

Basic New Action Testing ActionController::TestCase

I am somewhat new to testing in rails, and I'm wondering if this is a sufficient test for my new controller.
test "should get new" do
get :new
assert_response :success
end
Controller:
def new
#question = Question.new
end
Because the new action stores the controller in memory and does not write it to the DB, nor does it validate it. This seems sufficient to me. Any thoughts?
Controller testing should generally assert a few things
Controller rendered the correct template
You redirected to the right place
The instance variable has the correct data
Sometimes I also send some extra post variables in to make sure someone isn't goin to be able to curl themselves into an admin.

How to test that a before_filter works correctly with RSpec in Rails

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

Rails: RSpec controller test passes without action being implemented, why?

I have the following RSpec example which is passing:
describe UsersController do
it "should render new template on new action" do
get :new
response.should render_template("users/new")
end
end
The problem is I have not implemented the 'new' action on the UsersController.
Can anyone tell me why this test is passing?
I am new to RSpec, so any tips would be greatly appreciated!
When requesting an action for which a view exists, but the action is not defined, Rails will simply render the view. Therefore your spec (correctly) passes.
In addition to this spec, you may want to test for the assignment of particular instance variables. Example:
it "should assign the found articles for the view" do
assigns[:article].should == [#article]
end

Resources