How do you fake cookies in capybara? - ruby-on-rails

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}")

Related

Using Session, Cookies or Current_something in Rspec request tests

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

Rails 5 - integration tests NoMethodError: undefined method `signed' for #<Rack::Test::CookieJar:0x00000006796390>

I've been looking for solution for quite a long time but I can't really understand what the actual problem is. I'm following Ruby On Rails Tutorial by Michael Hartl and trying to make some integration tests for login. After a few modifications the code in test file looks like this:
test 'login without remembering' do
login_as #user, remember: '1'
delete logout_path
login_as #user, remember: '0'
assert_not session[:user_id].nil?
assert_nil cookies[:remember_token]
assert_nil cookies[:user_id]
end
test 'login with remembering' do
login_as #user, remember: '1'
assert logged_in?
assert_not_empty cookies[:remember_token]
assert_not_empty cookies[:user_id]
assert session[:user_id].nil?
assert_equal assigns(:user).remember_token, cookies[:remember_token]
end
logged_in? method is avaible by include SessionsHelper in ActionDispatch::IntegrationTest. It's core part is to find an user:
if (user_id = session[:user_id]) # User is not remembered
#current_user = User.find_by(id: user_id)
elsif (user_id = cookies.signed[:user_id]) # User is remembered
user = User.find_by(id: user_id)
remember_token = cookies[:remember_token]
(#current_user = user) if user && user.authenticated?(remember_token)
end
However, because test cookie jar is Rack::Test::CookieJar this simply doesn't work and raises NoMethodError: undefined method 'signed' for #<Rack::Test::CookieJar:0x00000006796390>
This came in useful and helped me solve the problem. After some time thinking I did that:
class ActionDispatch::IntegrationTest
# Get ActionDispatch::Cookies::CookieJar because Rack::Test::CookieJar
# doesn't support signed cookies
def cookies
cookies = ActionDispatch::Request.new(Rails.application.env_config).cookie_jar
end
include SessionsHelper
def login_as(user, password: 'Password', remember: '0')
post login_path, params: { session: { email: user.email,
password: password,
remember: remember } }
# Set cookies manually
return if remember == '0'
user = assigns(:user)
return if user.remember_token.nil?
cookies[:remember_token] = user.remember_token
cookies.signed[:user_id] = user.id
end
end
However, I still don't understand why it solves my problem and whether it's a good solution. If I don't assign cookie_jar from requested object to cookies variable in cookies method, assert session[:user_id].nil? fails even though I log in with remembering (for me it means using cookies instead of session). I'm trying to wrap my head around that but it's beyond my capabilities.
EDIT:
It's getting ever weirder. It looks like I get different errors after running tests several times. Some of them just turn up from time to time.
Well, none of those modifications solved my problem to be honest, some of my tests started to behave inconsistently.
I've decided to thumb through Ruby On Rails Tutorial and discovered that instead of trying to replace the default cookie jar in integration test I can simply check cookies in helper test. It turns out that in tests for helpers the cookie jar is not Rack::Test::CookieJar but ActionDispatch::Cookies::CookieJar which is fine and works with signed method. For integration tests I simply rely on modified logged_in? method which tests only session[:user_id] and looks like this:
class ActiveSupport::TestCase
def logged_in?
!session[:user_id].nil?
end
end
To ensure that cookies works, I created a suitable test for SessionsHelper which can be found in rails tutorial.
What is also important is that I underestimated session. I've decided to store user's id there and treat it as a main indicator that the user is logged in, thereby using cookies only for remembering users. In this way I don't have to worry about testing cookies in integration tests which seems to be faulty.
Big thanks to OnlySteveH who helped me out by asking some useful questions.

Rails Pundit Policy Spec test failing with NoMethodError

I have just started using Pundit for authorization in my current project along with the pundit-matchers gem.
So far it seems to generally be working for me but I have a problem in my tests.
I have generally tried to follow the examples in the pundit-matchers readme and the Thunderbolt labs blog (http://thunderboltlabs.com/blog/2013/03/27/testing-pundit-policies-with-rspec/).
This is my policy file;
#app/policies/procedure_policy.rb
class ProcedurePolicy
attr_reader :user, :procedure
def initialize(user, procedure)
#user = user
#procedure = procedure
end
def index?
user.admin?
end
end
And this is my policy_spec file
require 'rails_helper'
describe ProcedurePolicy do
subject {described_class.new(user, procedure)}
let(:procedure) {FactoryGirl.create(:procedure)}
context "for a guest" do
let(:user) {nil}
it {is_expected.not_to permit_action(:index)}
end
context "for a non-admin user" do
let(:user) {FactoryGirl.create(:user)}
it {is_expected.not_to permit_action(:index)}
end
context "for an admin user" do
let(:user) {FactoryGirl.create(:admin_user)}
it {is_expected.to permit_action(:index)}
end
end
2 of my 3 tests pass; The "for a non-admin user" and "for an admin user" ones. The "for a guest" test fails with
NoMethodError:
undefined method `admin?' for nil:NilClass
Now I understand why. I'm passing nil to the #index? method of my ProcedurePolicy class which will not have an #admin? method. But all of the example specs I have found online do exactly this. What am I not seeing.
Apologies if I'm missing something really obvious. I've been away from coding for a couple of years.
Pundit calls the current_user method to set user, and typically that is provided by an authentication system like Devise or a custom solution. This means that in most scenarios, the expectation is that you always have a layer of authentication before you hit the Pundit logic, so you never have a user set to nil when it gets there.
If you want Pundit authorizations to work directly without authentication, you have to handle that in your policy definitions, ex:
class ProcedurePolicy
def index?
user.present? && user.admin?
end
end
http://www.rubydoc.info/gems/pundit#Policies
The "being a visitor" context is for testing authorisation attempts when there is no user currently authenticated in the system. Since no user is logged in, no user record/object exists - the absence of a user object is nil. This is why both the Thunderbolt Labs and pundit-matchers examples use nil to represent a visitor. The nil object does not have an admin? method, causing the error.
To correctly handle nil/guest users in your policy, check that the user object is present before checking if the user is an admin, either by checking the user object directly or using the present? method in Rails, e.g.
def index?
user.present? && user.admin?
end
or just:
def index?
user && user.admin?
end
You'll find that authentication systems such as Devise return nil from the current_user method when no user is signed in.

Can't get an authorized user in integration test with Rails 3.1/Authlogic/ActiveRecord session store

I am trying to write a simple integration test on a Rails 3.1 application that uses Authlogic and ActiveRecord SessionStore, but I'm hitting the wall.
First, I tried a classic approach: after ensuring that require "authlogic/test_case" line is already in our test_helper.rb I then wrote a setup method that calls activate_authlogic and then uses UserSession(#user) to create a session.
This did create a session (as proved by UserSession.find), but when doing a get request on protected resource, the response would be a redirect to the login form and session was killed (ie. UserSession.find would return nil)
I tried POST-ing the email/password as well, but that seems to work only if I change the session store back to cookie_store (something I found out from this comment).
Switching session store to CookieStore just for test would be an option, but there are some tests that already depend on ActiveRecord store.
Is there any way to switch the session store just for one test? Is there any other solution to this problem that I'm missing?
require 'test_helper'
class ProtectedTest < ActionController::IntegrationTest
def setup
activate_authlogic
#user = Factory(:user, :roles => 'user')
UserSession.create(#user)
end
def test_protected
https!
get '/protected'
assert_response :success
end
end
Dear people from the future:
I figured a workaround to this by starting tests that needed authorized users in a different environment by setting ENV["RAILS_ENV"] before including 'test_helper'. Then I changed session_store in that environment to cookie_store.
ENV["RAILS_ENV"] = "test_cs"
require 'test_helper'
end
class ProtectedTest < ActionController::IntegrationTest
# ...
end

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)

Resources