I have this code to authenticate channel subscribers:
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
protected
def find_verified_user
if current_user = User.find_by(id: cookies.signed[:user_id])
current_user
else
reject_unauthorized_connection
end
end
end
end
And everything works fine. The problem is in feature tests.
When I run this test:
require 'rails_helper'
feature 'Chat room' do
scenario "send one message" do
user = create(:user)
login_as(user, :scope => :user)
expect {
fill_in 'message', with: 'hello friend'
click_button 'Send'
byebug
}.to change(Message, :count).by(1)
expect(current_path).to eq root_path
expect(page).to have_content 'hello friend'
end
end
The test log, says that the "An unauthorized connection attempt was rejected". Since the cookie is empty, it is not able to authenticate.
So how can I set the cookies in capybara tests?
I tried something do this cookies.signed[:user_id] = user.id in the test but it does not work.
How can I set the cookie like this cookies.signed[:user_id] = user.id in tests?
Assuming that the login_as you're calling is from the Warden test helpers, what it does is set up so that the next request sets the session cookie in the response. Because of this you probably need to visit a page after calling login_as. Additionally, since clicking 'Send' is asynchronous you need to wait for something to change before checking that Message.count has changed, and you really shouldn't be using with .eq with current_path if you want non-flaky tests. So all combined something like
#don't visit the page where you can fill in the message before calling login_as
scenario "send one message" do
user = create(:user)
login_as(user, :scope => :user)
visit 'the path to the page where you can fill in a message'
expect {
fill_in 'message', with: 'hello friend'
click_button 'Send'
expect(page).to have_css('#messages', text:'hello friend') # adjust selector depending on where the message is supposed to appear
expect(page).to have_current_path(root_path)
}.to change(Message, :count).by(1)
end
should work for you
Related
I have a test case where after registration devise signup(resource_name, resource) method is called and a redirect_url is sent back to front end , which in turns load the requested page by window.location = redirect_url, though when the next page is hit the next page controller is getting current_user as nil.
Test Case
require 'rails_helper'
feature "sign up with investment profile", js: true do
context 'all fields are valid' do
let(:goal) do
create(
:goal,
risk_level: Tags::BALANCED,
age: 30
)
end
before 'valid sign up steps' do
create(:currency_conversion)
f = File.read("#{Dir.pwd}/spec/response/marketplace/performance.json")
allow(Marketplace::StrategyService).to receive(:get_performance_trail).and_return(JSON.parse(f))
model_portfolio_version = create(:model_portfolio_version, :with_performance_trails)
goal.model_portfolio_version = model_portfolio_version
goal.save!
goal.reload
visit_page_containing_ajax "/suggestions/#{goal.url_hash}"
click_link 'Next: Personal Details'
within('.signup-page-header') do
expect(page).to have_content 'SIGN UP'
end
fill_registration_fields(signup_email)
fill_personal_info_form
wait_for_ajax
find('.btn-primary').click
wait_for_ajax
fill_identification_form
wait_for_ajax
find('.btn-primary').click
wait_for_ajax
fill_declaration_form
wait_for_ajax
find('.btn-primary').click
wait_for_ajax
expect(page).to have_current_path(kyc_dashboard_path(goal.url_hash))
end
context 'User should get registered and redirected to kyc dashboard' do
let(:signup_email) { 'email#example.com' }
context 'user' do
it 'creates investor account' do
expect(page).to have_content('Your registration is successful')
end
end
end
end
end
PS: This is happening in test environment only
After debugging for hours , I found that we are maintaining session in active_record_store when i change it to cookie_store spec works fine.
I have following RSpec test:
require 'rails_helper'
require 'spec_helper'
RSpec.describe "Users", type: :request do
describe "sign in/out" do
describe "success" do
it "should sign a user in and out" do
attr = {:name=>"Test1",
:email => "dmishra#test.org",
:password => "foobar",
:password_confirmation => "foobar"
}
user = User.create(attr)
visit signin_path
fill_in "Email", :with => user.email
fill_in "Password", :with => user.password
puts page.body
click_button "Sign in"
controller.should be_signed_in
click_link "Sign out"
controller.should_not be_signed_in
end
end
end
end
I am getting the following error:
Failure/Error: controller.should be_signed_in
expected to respond to `signed_in?
This is because controller is nil. What is wrong here which causes controller to be nil?
Controller class is:
class SessionsController < ApplicationController
def new
#title = "Sign in"
end
def create
user = User.authenticate(params[:session][:email],
params[:session][:password])
if user.nil?
flash.now[:error] = "Invalid email/password combination."
#title = "Sign in"
render 'new'
else
sign_in user
redirect_to user
end
end
def destroy
sign_out
redirect_to root_path
end
end
signed_in method is defined in session helper which is included.
Ruby platform information:
Ruby: 2.0.0p643
Rails: 4.2.1
RSpec: 3.2.2
This is a request spec (which is basically a rails integration test) which is designed to span multiple requests, possibly across controllers.
The controller variable is set by the request methods that integration testing provides (get, put, post etc.)
If instead you use the capybara DSL (visit, click etc.) then the integration test methods never get called and accordingly controller will be nil. When using capybara you don't have access to individual controller instances so you can't test things such as what signed_in? returns - you have to test a higher level behaviour (e.g. what is on the page).
I'm working on exercise 9 from chapter 9 in Rails Tutorial: http://ruby.railstutorial.org/chapters/updating-showing-and-deleting-users#fnref-9_9
"Modify the destroy action to prevent admin users from destroying themselves. (Write a test first.)"
I started with creating test:
users_controller_spec.rb
require 'spec_helper'
describe UsersController do
describe "admins-userscontroller" do
let(:admin) { FactoryGirl.create(:admin) }
let(:non_admin) { FactoryGirl.create(:user) }
it "should not be able to delete themself" do
sign_in admin
expect { delete :destroy, :id => admin.id }.not_to change(User, :count)
end
end
end
however, noticed that even if logic for prohibiting admin to delete himself is not implemented, test passes unless I change line
sign_in admin
to
sign_in admin, no_copybara: true
after this change test fails (as expected)
sign_in is in support\utilities.rb file and looks like this:
def sign_in(user, options={})
if options[:no_capybara]
# Sign in when not using Capybara.
remember_token = User.new_remember_token
cookies[:remember_token] = remember_token
user.update_attribute(:remember_token, User.hash(remember_token))
else
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
end
end
Does anyone know why it doesn't work with capybara? It looks like the "else" section of the above code fails/doesn't execute when using capybara but it doesn't return any error (e.g. that "Email" field is not found, so it looks like it's rendered)...
Other problem is that if I remove non_admin instead of admin:
expect { delete :destroy, :id => non_admin.id }.not_to change(User, :count)
test passes which means non_admin isn't deleted... why does it work for admin and not non_admin?
Question 2:
capybara is not supposed to work in request spec as of 2.0+, but Im using capybara 2.1 and rspec-rails 2.13.1 and it works just fine in request specs (actually that's even what tutorial tells us to do), doesn't even output any warning...
I'm implementing a lazy login feature. My cucumber feature should describe it:
Feature: User log in
Scenario: Lazy login
Given I didn't log out the last time I was on the site
When I go to the homepage
Then I should automatically be logged in
And these are my step definitions:
Given(/^I didn't log out the last time I was on the site$/) do
user = FactoryGirl.create(:user)
visit new_user_session_path
fill_in('user[email]', with: user.email)
fill_in('user[password]', with: 'test123')
click_button('Sign in')
Capybara.reset_sessions!
end
When(/^I go to the homepage$/) do
visit root_path
end
Then(/^I should automatically be logged in$/) do #<-- Fails here
page.should have_content("Logout")
end
This is what happens when a user logs in: the cookies.signed[:auth_token] gets set. This will be used by a before filter in my ApplicationController so that users who open a fresh browser will be logged in automatically:
class SessionsController < Devise::SessionsController
def create
super
if user_signed_in?
puts 'yesssssss'
session[:user_id] = current_user.id
current_user.remember_me! if current_user.remember_token.blank?
cookies.signed[:auth_token] = {
:value => current_user.remember_token,
:domain => "mysite.com",
:secure => !(Rails.env.test? || Rails.env.development?)
}
puts "current_user.remember_token = #{current_user.remember_token}"
puts 'cookies:'
puts cookies.signed[:auth_token]
end
end
end
This is the before filter in my ApplicationController:
def sign_in_through_cookie
logger.info "logging in by cookie"
puts "logging in by cookie"
puts cookies.signed[:auth_token] #<-- PROBLEM: this returns nil.
return true if !current_user.nil?
if !cookies[:auth_token].nil? && cookies[:auth_token] != ''
user = User.find_by_remember_token(cookies.signed[:auth_token])
return false if user.blank?
sign_in(user)
puts 'success'
return true
else
return false
end
end
So the issue is that in the last step of my cucumber feature, cookies.signed[:auth_token] returns nil. I'm guessing this is just a capybara thing. So do I actually have to set a cookie in the test as opposed to using the one in my controller?
So eventually I figured it out after trying a lot of different things.
Given(/^I didn't log out the last time I was on the site$/) do
user = FactoryGirl.create(:user)
visit new_user_session_path
fill_in('user[email]', with: user.email)
fill_in('user[password]', with: 'test123')
click_button('Sign in')
Capybara.current_session.driver.request.cookies.[]('auth_token').should_not be_nil
auth_token_value = Capybara.current_session.driver.request.cookies.[]('auth_token')
Capybara.reset_sessions!
page.driver.browser.set_cookie("auth_token=#{auth_token_value}")
end
When(/^I go to the homepage$/) do
visit root_path
end
Then(/^I should automatically be logged in$/) do
page.should have_content("Logout")
end
UPDATE:
Here's what I use in case I'm using Selenium for some of the tests:
if Capybara.current_session.driver.class == Capybara::Selenium::Driver
auth_token = page.driver.browser.manage.cookie_named('auth_token')[:value]
page.driver.browser.manage.delete_all_cookies
page.driver.browser.manage.add_cookie(:name => "auth_token", :value => auth_token)
else
puts "cookies = #{Capybara.current_session.driver.request.cookies}"
Capybara.current_session.driver.request.cookies.[]('auth_token').should_not be_nil
auth_token_value = Capybara.current_session.driver.request.cookies.[]('auth_token')
Capybara.reset_sessions!
page.driver.browser.set_cookie("auth_token=#{auth_token_value}")
end
Use https://github.com/nruth/show_me_the_cookies which wraps the driver methods. It has methods for getting cookies, deleting cookies, and a method for creating cookies called create_cookie.
I needed just to test the cookie values
Inspiration taken from https://collectiveidea.com/blog/archives/2012/01/05/capybara-cucumber-and-how-the-cookie-crumbles
and ported to Rails 5.x
Create features/support/cookies.rb
With content
module Capybara
class Session
def cookies
#cookies ||= ActionDispatch::Request.new(Rails.application.env_config.deep_dup).cookie_jar
end
end
end
Before do
allow_any_instance_of(ActionDispatch::Request).to receive(:cookie_jar).and_return(page.cookies)
allow_any_instance_of(ActionDispatch::Request).to receive(:cookies).and_return(page.cookies)
end
Then the step for testing
Then('is set cookie {string} with value {string}') do |cookie, value|
expect(page.cookies.signed[cookie]).to eq value
end
Inside a controllers test, I want to test that when logged in, the controller renders the request fine, else if not logged in, it redirects to the login_path.
The first test passes fine as expected, no user is logged in, so the request is redirected to the login_path. However I've tried a myriad of stub/stub_chain's but still can't get the test to fake a user being logged in and render the page okay.
I would appreciate some direction on getting this to work as expected.
The following classes and tests are the bare bones to keep the question terse.
ApplicationController
class ApplicationController < ActionController::Base
include SessionsHelper
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
end
SessionsHelper
module SessionsHelper
def logged_in?
redirect_to login_path, :notice => "Please log in before continuing..." unless current_user
end
end
AppsController
class AppsController < ApplicationController
before_filter :logged_in?
def index
#title = "apps"
end
end
apps_controller_spec.rb
require 'spec_helper'
describe AppsController do
before do
#user = FactoryGirl.create(:user)
end
describe "Visit apps_path" do
it "should redirect to login path if not logged in" do
visit apps_path
current_path.should eq(login_path)
end
it "should get okay if logged in" do
#stubs here, I've tried many variations but can't get any to work
#stubbing the controller/ApplicationController/helper
ApplicationController.stub(:current_user).and_return(#user)
visit apps_path
current_path.should eq(apps_path)
end
end
end
This is not working because you are stubbing the method current_user on the ApplicationController class, and not an instance of that class.
I would suggest stubbing it (correctly) on an instance of that class, but your test appears to be an integration test rather than a controller test.
What I would do instead then is as Art Shayderov mentioned is to emulate the sign-in action for a user before attempting to visit a place that requires an authenticated user.
visit sign_in_path
fill_in "Username", :with => "some_guy"
fill_in "Password", :with => "password"
click_button "Sign in"
page.should have_content("You have signed in successfully.")
In my applications, I've moved this into a helper method for my tests. This is placed into a file at spec/support/authentication_helpers.rb and looks like this:
module AuthenticationHelpers
def sign_in_as!(user)
visit sign_in_path
fill_in "Username", :with => user.username
fill_in "Password", :with => "password"
click_button "Sign in"
page.should have_content("You have signed in successfully.")
end
end
RSpec.configure do |c|
c.include AuthenticationHelpers, :type => :request
end
Then in my request specs, I simply call the method to sign in as that particular user:
sign_in_as(user)
Now if you want to sign in using a standard controller test, Devise already has helpers for this. I generally include these in the same file (spec/support/authentication_helpers.rb):
RSpec.configure do |c|
c.include Devise::TestHelpers, :type => :controller
end
Then you can sign in using the helpers like this:
before do
sign_in(:user, user)
end
it "performs an action" do
get :index
end
I would look at http://ruby.railstutorial.org/chapters/sign-in-sign-out#sec:a_working_sign_in_method.
The author describes how to write a sign_in method and use it in your rspec tests.
It doesn't look like controller test. It looks more like rspec-rails request spec which simulates browser. So stabbing controller won't work, you have to either simulate sign in (something like this)
visit sign_in
fill_in 'username', :with => 'username'
...
or manually add user_id to session.
If on the other hand you want to test controller in isolation your test should look like that:
get 'index'
response.should be_success