Using Session, Cookies or Current_something in Rspec request tests - ruby-on-rails

I am trying to follow the new Rails testing convention with this new project I am working on. Therefore I setup unit tests, request tests and feature tests. The issue is that in the request tests, Capybara and the the session information are not supported.
In addition to having the current_user method setup by Devise, my application has another similar method called current_client. For some of my controllers, I need to check whether the current_user is logged in. This works with Devise authenticate_user! called in a before_action. But for some controllers, I also need to check if a client was selected first (for example if you want to add transactions, they need to be tied with the current client being worked on).
So I added another before_action method that checks if a client was also selected. This works well in feature tests with Capybara where I can mimic user loggin in and the user selecting a client to process. However in the request tests, It doesn`t work.
I first test trying to access the endpoint without a user being logged in and the response is not successful (as it should) and it redirects to the sign in page. But then I need to run my tests with a user logged in AND a client selected. Devise helpers provide a sign_in(user) method. However I can't get my current_user method to work and I can't seem to be able to set it up properly. So those tests are failing and redirecting to another page asking the user to select a client.
I have already tried many suggestions I saw. Like trying to stub the current_client method, trying to provide session info to the GET call. I even tried to check the Devise source code to find out how they can simulate the current_user method in Rspec and couldn't really find where the magic happens.
Here is the code
The current_client method looks like this
def current_client
current_client ||= Client.find(session[:client_id]) if session[:client_id]
rescue ActiveRecord::RecordNotFound
current_client = nil
end
This is how it is set once a user selects a client to process
def set_current_client(client)
session[:client_id] = client.id
end
This is the code in my test file
I first create 2 users so that I can test that user 1 cannot access user 2 transactions down the line. (I'm using FactoryBot)
I then create 2 clients (one for each user)
RSpec.describe "Rooms", :type => :request do
let!(:user) {create(:user)}
let!(:user2) {create(:user2)}
let!(:client) {create(:client, user: user)}
let!(:client) {create(:client, user: user2)}
The following works
describe 'User Not Signed In' do
describe 'GET #index' do
subject { get transact_path}
it "returns an unsuccessful response" do
subject
expect(response).to_not be_successful
end
it "redirects to sign in page" do
subject
expect(response).to redirect_to (new_user_session_path)
end
end
The following doesn't. The sign_in(user) method works in the before block and the redirect is not made to the sign in page. However, the response is not successful because the current_client is not set and I have tried to set it in so many ways in the before block without success.
describe 'User Signed In' do
before do
sign_in(user)
end
describe 'GET #index' do
it "returns a successful response" do
get transact_path
expect(response).to be_successful
end
end
end
I understand that the best practices moved away from controller tests because what view rendered or what instance variable assigned doesn't have anything to do with controllers in theory. But in my case, I'm simply trying to test my endpoints and right now I can't because I cannot setup my current_client.

I found a way around it by using a suggested solution by DHH himself.
Instead of trying to stub the current_client variable or try to jury rig something in the gut of ActionDispatch::Cookies, you simply need to do a POST or GET call to whatever controller is responsible for setting my current_client variable.
So for me the solution was to put the following code in a before do block
before do
sign_in(user)
get select_client_path, params: {id: client.id}
end

Related

How do you fake cookies in capybara?

I am testing an app that would be installed on a sub domain to take advantage of the cookies created by the main app. How ever unless both apps are running on, say: site.local.com the app I am testing doesn't have access to a session controller, thus I cannot create cookies in testing via typical sign in methods.
So my question is, how do I - in capybara - fake cookies? I have tried something like:
it "should ... ", type: :request do
sign_in(#user)
...
end
#sign_in definition:
def sign_in(user)
cookies.signed[:auth_token] = user.auth_token
end
How ever the issue is:
Failure/Error: sign_in(#user)
NameError:
undefined local variable or method `cookies' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_2:0x000000090cd998>
Try this:
page.driver.browser.set_cookie("auth_token=#{user.auth_token}")

How to mock CanCan's "authorize!" method in a controller spec?

The simplest way to write specs checking that a controller is authorizing the signed-in current_user to use the said action is to explicitly write two specs checking that a user with permission can run that action, and another cannot. For example:
# The controller action:
def some_action
authorize! :do_this_action, #some_object
end
# The spec:
it "should work when authorized" do
sign_in #user
get :some_action
response.should be_success
end
it "should not work when not authorized" do
sign_in #other_user
get :some_action
response.should_not be_success
end
But this is not great, as it requires a great amount of code (two or more specs) and doesn't actually test directly that the authorization is being checked (do_this_action in the example).
I would like to be able to write a simpler spec, e.g. (an example using 'rr' for mocking)
it "should check the user can do_this_action" do
mock(controller).authorize!(:do_this_action, some_object)
get :some_action
end
I think this would be shorter and directly test that the authorization check I want to be used is being run. But, I can't figure out how to write the mock. mock(controller) isn't correct, nor does mocking ApplicationController or Ability seem to work.
How do I write this kind of mock?
I believe it would be better to test authorization in another spec like ability_spec.rb in order for you to separate their responsibilities. By doing so you won't have to test the same authorization method that you use on different controllers.
heres some other details
It turns out the way I was writing above works.. must have had a typo earlier.
So, to check that an authorize! check is invoked in the controller, you just need to write:
it "should check the user can do_this_action" do
mock(controller).authorize!(:some_action, some_object)
get :some_action
end

Devise won't sign in properly in controller test with FactoryGirl

I've got some funny behavior when trying to get Devise to sign in properly in my controller testing. It seems to work in certain cases, but not in others. I'm not sure if this is an interaction between Devise and FactoryGirl or something else at work.
First off, here's my factories:
factory :advisor do
name "Jason Jones"
association :user
initialize_with {Advisor.find_or_create_by_name('Jason Jones')}
end
factory :client do
name "Rich Homeowner"
association :advisor
end
factory :user do
email "jason#jones.com"
password "testpassword"
initialize_with {User.find_or_create_by_email('jason#jones.com')}
end
my controller:
class ClientsController < ApplicationController
before_filter :authenticate_user!
def destroy
#client = current_user.advisor.clients.where(:id => params[:id]).first
#client.destroy
flash[:notice] = 'Client deleted.'
redirect_to clients_path
end
and my controller test:
describe "DELETE destroy" do
it "should delete a client" do
a = FactoryGirl.create(:advisor)
c = FactoryGirl.create(:client, :advisor => a)
login_user(a.user)
expect{
delete :destroy, :id => c.id
response.should be_redirect
assigns(:client).should eq(c)
}.to change(Client, :count).by(-1)
end
end
the login_user spec helper is where it gets funky. if I uncomment the line below, forcing the user to be set to the FactoryGirl object, the test passes. If I leave it commented, Devise attempts to sign in as the passed user (which I have verified via debugging is the same user in the DB), but it does not actually sign in. The sign_in call actually returns the same array in both cases, but based on following the execution path, the controller code is never executed, because Devise redirects to the login page.
def login_user(user=nil)
#request.env["devise.mapping"] = Devise.mappings[:user]
if user.nil?
user = FactoryGirl.create(:user)
end
# user = FactoryGirl.create(:user) # uncommenting this line causes test to pass
sign_in user
end
How do I get the sign_in to work properly?
For the record, when it comes to TDD for Rails, I spend 10 min getting my actual code to work properly and 2 hours jumping through hoops to get my test code to do what it's supposed to.
I recently started trying to swallow the philosophy of TDD and admit that I had the same exact feeling as you did at first. Your time estimates seem pretty accurate, 10 minutes of development and 2 hours of test case implementation. My first advice is, like many things in life, it gets better. The first time you make a seemingly innocuous change then realize that you've broken half your test regression you'll be glad you took the pill.
With that said, this is going to sound like a cop out because you're asking why Devise doesn't work and my answer is: you shouldn't care. Clearly you're doing something wrong, and I'm afraid I can't tell what it is from the information given, but I think I can help anyway.
The only thing I see wrong above is that your spec is testing at least four things:
Response is a redirect.
#client is assigned.
A Client is destroyed.
Devise is providing proper authentication.
A spec should test one thing and one thing only. As tempting as it may be to test more, I don't recommend it. Cucumber or other integration tests test bunches of things, but not specs.
Devise is not something you should test here, so stub it out. I think something like this will work:
before :each do
#advisor = FactoryGirl.create(:advisor)
controller.stub(:authenticate_user!).and_return(true)
controller.stub(:current_user).and_return(#advisor.user)
end
After this, create three different it "should" do blocks for the three things you're testing.
Another tip is I don't think you need to specify association when defining a FactoryGirl factory. I think this is only needed for polymorphic associations. Normally you can just give the name of the association without a value and it will run the factory with the same name. Just watch out for infinite loops.
I hope that helps.

How to stub ApplicationController method in request spec

I am needing to stub the response of a current_user method in an Rspec/capybara request spec. The method is defined in ApplicationController and is using helper_method. The method should simply return a user id. Within the test, I'd like this method to return the same user id each time.
Alternatively, I could fix my problem by setting session[:user_id] in the spec (which is what current_user returns)... but that doesn't seem to work either.
Are either of these possible?
Edit:
Here is what I've got (it is not working. It just runs the normal current_user method).
require 'spec_helper'
describe "Login" do
before(:each) do
ApplicationController.stub(:current_user).and_return(User.first)
end
it "logs in" do
visit '/'
page.should have_content("Hey there user!")
end
end
Also not working:
require 'spec_helper'
describe "Login" do
before(:each) do
#mock_controller = mock("ApplicationController")
#mock_controller.stub(:current_user).and_return(User.first)
end
it "logs in" do
visit '/'
page.should have_content("Hey there user!")
end
end
skalee seems to have provided the correct answer in the comment.
If the method you're trying to stub is an instance method (most likely) and not a class method then you need use:
ApplicationController.any_instance.stub(:current_user)
Here are a couple of examples of the basic form.
controller.stub(:action_name).and_raise([some error])
controller.stub(:action_name).and_return([some value])
In your particular case, I believe the proper form would be:
controller.stub(:current_user).and_return([your user object/id])
Here's a full working example from a project I work on:
describe PortalsController do
it "if an ActionController::InvalidAuthenticityToken is raised the user should be redirected to login" do
controller.stub(:index).and_raise(ActionController::InvalidAuthenticityToken)
get :index
flash[:notice].should eql("Your session has expired.")
response.should redirect_to(portals_path)
end
end
To explain my full example, basically what this does is verify that, when an ActionController::InvalidAuthenticityToken error is raised anywhere in the app, that a flash message appears, and the user is redirected to the portals_controller#index action. You can use these forms to stub out and return specific values, test an instance of a given error being raised, etc. There are several .stub(:action_name).and_[do_something_interesting]() methods available to you.
Update (after you added your code): per my comment, change your code so it reads:
require 'spec_helper'
describe "Login" do
before(:each) do
#mock_controller = mock("ApplicationController")
#mock_controller.stub(:current_user).and_return(User.first)
end
it "logs in" do
visit '/'
page.should have_content("Hey there user!")
end
end
This works for me and gives me a #current_user variable to use in tests.
I have a helper that looks like this:
def bypass_authentication
current_user = FactoryGirl.create(:user)
ApplicationController.send(:alias_method, :old_current_user, :current_user)
ApplicationController.send(:define_method, :current_user) do
current_user
end
#current_user = current_user
end
def restore_authentication
ApplicationController.send(:alias_method, :current_user, :old_current_user)
end
And then in my request specs, I call:
before(:each){bypass_authentication}
after(:each){restore_authentication}
For anyone else who happens to need to stub an application controller method that sets an ivar (and was stymied by endless wanking about why you shouldn't do that) here's a way that works, with the flavour of Rspec circa October 2013.
before(:each) do
campaign = Campaign.create!
ApplicationController.any_instance.stub(:load_campaign_singleton)
controller.instance_eval{#campaign = campaign}
#campaign = campaign
end
it stubs the method to do nothing, and sets the ivar on rspec's controller instance, and makes it available to the test as #campaign.
For Rspec 3+ the new api is:
For a controller test, nice and short:
allow(controller).to receive(:current_user).and_return(#user)
Or for all instances of ApplicationController:
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(#user)
None of the provided responses worked for me. As in #matt-fordam's original post, I have a request spec, not a controller spec. The test just renders the view without launching a controller.
I resolved this by stubbing the method on the view as described in this other SO post
view.stub(:current_user).and_return(etc)

Staying DRY while testing a controller, authorized via CanCan

I'm retroactively writing some tests, using RSpec, for a Rails project.
I'm using the CanCan gem to provide authorization. I decided to write a spec that will test the ability.rb model.
I then went on to test my remaining models.
I've moved on to controllers, and I've run into a huge snag: I'm testing my abilities all over again!
Basically, I have to stub out a series of models, and stub out their associations; otherwise the response just returns 403 Forbidden.
The reason for this, is that the controller is basically in charge of worrying about authorization.
I'm not quite sure where to go from here. I'm stubbing out up to 6 models, just to write a single test.
I know the abilities work, that's what ability_spec.rb is for.
So this question is really 2-fold:
Should I be testing the ability model separately?
Should the controller tests be concerned with proper permissions?
Edit
require 'spec_helper'
include Devise::TestHelpers # to give your spec access to helpers
describe TokensController do
before(:each) do
#mock_user = User.new(:username => "bob", :email => "user#user.com", :password => "longpassword")
#mock_user.role = "admin"
sign_in #mock_user
#Ability.stub!('can').and_return(true)
end
it "should let me see grids/:g_id/tokens index" do
test_grid = mock_model(Grid)
test_token = mock_model(Token)
Grid.stub!(:find).and_return(test_grid)
Token.stub!(:find).and_return(test_token)
get 'index'
a1 = Ability.new(#mock_user)
a1.can?(:index, Token).should be_true # This line works fine; as it should
puts response.status #This returns 403, which means CanCan::AccessDenied was raised
end
end
Thanks,
Robbie
Not sure if this is too late for you, but I just ran into the same issue, and solved it using the following code sample --
before do
#user = Factory.create(:user)
sign_in #user
#abilities = Ability.new(#user)
Ability.stub(:new).and_return(#abilities)
end
end
I've stubbed out Ability#new, giving me a reference to the instance of Ability that controls the current user. Then, I can stub out specific abilities like this:
#abilities.stub!(:can?).with(:destroy, regatta).and_return(true)
or give admin privileges:
#abilities.stub!(:can?).and_return(false)
I do test the cancan model separately, but testing what it will allow in what conditions.
I think if you are doing things like
authorize! :take_over, #the_world
Then I do think you should be testing that in the controller. I'm not sure you need to test ALL 6 versions of your models though.
You can stub out the Ability.can? class and have it respond true/false, and test how your controller handles when it can (and more importantly) when it cannot continue.
Similar to Sam's answer, but from the CanCan wiki page on testing:
Controller Testing
If you want to test authorization functionality at the controller level one option is to log-in the user who has the appropriate permissions.
user = User.create!(:admin => true) # I recommend a factory for this
# log in user however you like, alternatively stub `current_user` method
session[:user_id] = user.id
get :index
assert_template :index # render the template since he should have access
Alternatively, if you want to test the controller behavior independently from what is inside the Ability class, it is easy to stub out the ability with any behavior you want.
def setup
#ability = Object.new
#ability.extend(CanCan::Ability)
#controller.stubs(:current_ability).returns(#ability)
end
test "render index if have read ability on project" do
#ability.can :read, Project
get :index
assert_template :index
end
If you have very complex permissions it can lead to many branching possibilities. If these are all tested in the controller layer then it can lead to slow and bloated tests. Instead I recommend keeping controller authorization tests light and testing the authorization functionality more thoroughly in the Ability model through unit tests as shown at the top.
I think authorization needs to be done mainly for controllers to make sure your authorization is working correctly with your controllers. So to make it DRY you can implement your own matcher to be used like this
let!(:user) {create :user}
before { login_user_request user}
it "grants admin access to show action" do
expect{ get :show, {id: user.id} }.to be_authorized
end
it "denies user access to edit action" do
expect{ get :edit, {id: user.id} }.to be_un_authorized
end
and then implement these matchers with your own way to test how a request will be authorized or not
RSpec::Matchers.define :be_authorized do
match do |block|
block.call
expect(response).to be_success
end
def supports_block_expectations?
true
end
end
RSpec::Matchers.define :be_un_authorized do
match do |block|
expect{
block.call
}.to raise_error(Pundit::NotAuthorizedError)
end
def supports_block_expectations?
true
end
end
Why don't you include a
can :manage, :all do
user.is_ultrasuper == 1
end
in your Ability and then have a is_ultrasuper param in one of your fixture users:
one:
id: 1
username: my_username
is_ultrasuper: 1
Then log this user in at setup of your tests. This in tests you should be able to do anything at all.

Resources