Testing devise custom session controller with rspec - ruby-on-rails

I'm having custom controller
class Users::SessionsController < Devise::SessionsController
# POST /resource/sign_in
def create
binding.pry
super
end
end
routes added
devise_for :users, controllers: { sessions: "users/sessions" }
and it works during signin using browser. But inside controller test breakpoint inside create is not being hit:
RSpec.describe Users::SessionsController, type: :controller do
describe 'POST #create' do
context 'pending activation user with expired password' do
it 'could not login' do
user = create :operator_user, status: User.statuses[:activation_pending], password_changed_at: (1.day + 1.second).ago
#request.env['devise.mapping'] = Devise.mappings[:user]
sign_in user
user.reload
expect(user).to be_locked
end
end
end
end
RSpec.configure do |config|
#...
# Devise methods
config.include Devise::TestHelpers, type: :controller
# ...
end
I expect expression
sign_in user
to fall into create method that I've overrided. What am I doing wrong?
ps: it even falls into standard devise SessionsController#create

You have to send request to controller by using post :create, params: {...} inside your example instead of sign_in user

Related

How to test after_sign_in_path_for by minitest

I changed the default behavior of after_sign_in_path_for method like this:
class ApplicationController < ActionController::Base
private
def after_sign_in_path_for(resource)
return admin_root_path if resource.is_a?(AdminUser)
request.referrer || root_path
end
end
It works find and now I want to test it by minitest.
But I couldn't figure out how to write integration test for it.
Although there is an answer for rspec, I couln't rewrite for minitest.
How to test after_sign_in_path_for(resource)?
How can I write the test for after_sign_in_path_for by minitest?
Rails: 5.1
devise: 4.5.0
require 'test_helper'
class ApplicationControllerTest < ActionDispatch::IntegrationTest
include Devise::Test::IntegrationHelpers
setup do
2.times{ create(:post) }
#user = create(:user)
#admin_user = create(:admin_user)
end
test "should redirect to '/posts/1' after login" do
# get "/posts/1"
# sign_in(#user)
# test return back "/posts/1"
end
test "should redirect to '/posts/2' after login" do
# get "/posts/2"
# sign_in(#user)
# test return back "/posts/2"
end
test "should redirect to admin root page after login" do
# sign_in(#adminuser)
# test go to admin root page
end
end
You can test them like so:
require 'test_helper'
class ApplicationControllerTest < ActionDispatch::IntegrationTest
include Devise::Test::IntegrationHelpers
setup do
#user = create(:user)
#admin_user = create(:admin_user)
end
test "should redirect to current page after login" do
sign_in(#user)
get :index
assert_redirected_to controller: "home", action: "index"
end
test "should redirect to admin root page after login" do
sign_in(#adminuser)
get :index
assert_redirected_to controller: "admin", action: "index"
end
end
assert_redirected_to API docs

unable to use sign_in method in acceptance test Rspec

I recently added Devise and CanCanCan for authentication and permission in my rails project.
As a result it broke most of my acceptance tests I made previously , for example:
resource 'Projects' do
route '/projects?{}', 'Projects collection' do
get 'Return all projects' do
example_request 'list all projects' do
expect(status).to eq 200
Why this code is broken I do know : I have no #current_user, and CanCan rejected the request with CanCan::AccessDenied.
I am currently trying to authenticate an admin user, so that my acceptance test will pass, as I defined can :manage, :all for admin.
I stumbled across many posts like mine, but no solution worked, as all of answers I found were designed for controller testing, and sign_in method apparently worked for them.
What I tried so far:
before(:each) do
sign_in admin
end
NoMethodError:
undefined method `sign_in' for #<RSpec::ExampleGroups::Projects::ProjectsProjectsCollection::GETReturnAllProjects:0x0000000005dc9948>
So I tried to add
RSpec.configure do |config|
config.include Devise::TestHelpers
Failure/Error: #request.env['action_controller.instance'] = #controller
NoMethodError:
undefined method `env' for nil:NilClass
From what I understand I cannot do this because I am not testing in a controller scope, but I am testing a resource, so I have no #request neither #controller.
What am I doing wrong, and if not how can I make my test pass now that I included authentication & permission ?
versions used:
cancancan (2.2.0)
devise (4.3.0)
rails (5.1.4)
ruby 2.5.0p0
rspec (3.7.0)
The problem was exactly as described, I did not succeed in using Devise helpers in acceptance test.
Workaround for me was to adapt from acceptance test to request test.
# spec/requests/projects_spec.rb
require 'rails_helper'
RSpec.describe Project, type: :request do
let!(:admin) { create(:user, is_admin: true) }
let!(:user) { create(:user) }
context 'admin logged in' do
before do
log_in admin
end
it 'Return all projects' do
get projects_path
expect(status).to eq 200
// more tests
end
// more tests
end
context 'Normal user logged in' do
before do
log_in user
end
// more tests
end
The log_in method is from my own helper I created
# spec/support/session_helpers.rb
module SessionHelpers
def log_in(user, valid = true, strategy = :auth0)
valid ? mock_valid_auth_hash(user) : mock_invalid_auth_hash
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[strategy.to_sym]
get user_google_oauth2_omniauth_callback_path
end
end
It simply stub the authentication at a request level (note the get user_google_oauth2_omniauth_callback_path which is my app's authentication callback)
My callback is configured as such :
# app/config/routes.rb
Rails.application.routes.draw do
devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
devise_scope :user do
get 'sign_in', to: 'devise/sessions#new', as: :new_user_session
delete 'sign_out', to: 'devise/sessions#destroy', as: :destroy_user_session
end
# app/controllers/users/omniauth_callbacks_controller.rb
module Users
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
include Devise::Controllers::Rememberable
def google_oauth2
#user = User.from_omniauth(request.env['omniauth.auth'])
if #user
sign_in_and_redirect #user
else
redirect_to root_path, notice: 'User not registered'
end
end
// more code
end
end
Along with this other helper (my provider was Omniauth)
# spec/support/omniauth_macros.rb
module OmniauthMacros
def mock_valid_auth_hash(user)
# The mock_auth configuration allows you to set per-provider (or default)
# authentication hashes to return during integration testing.
OmniAuth.config.test_mode = true
opts = {
"provider": user.provider,
"uid": user.uid,
"info": {
"email": user.email,
"first_name": user.first_name,
"last_name": user.last_name
},
"credentials": {
"token": 'XKLjnkKJj7hkHKJkk',
"expires": true,
"id_token": 'eyJ0eXAiOiJK1VveHkwaTFBNXdTek41dXAiL.Wz8bwniRJLQ4Fqx_omnGDCX1vrhHjzw',
"token_type": 'Bearer'
}
}
OmniAuth.config.mock_auth[:default] = OmniAuth::AuthHash.new(opts)
end
def mock_invalid_auth_hash
OmniAuth.config.mock_auth[:default] = :invalid_credentials
end
end
And I required my modules so I can use them in my request tests.
# spec/rails_helper.rb
Dir[Rails.root.join('spec', 'support', '*.rb')].each { |file| require file }

devise "user_signed_in?" method in rspec

I have a controller action which uses devise's "user_signed_in?" as a condition. The action looks like this:
def show
if user_signed_in?
#do stuff
end
end
But when I am testing this action in RSpec, the tests are failing because the code inside the if block never gets executed. Is there any way to stub the "user_signed_in?" method?
You could mock user_signed_in? method like this:
before do
allow_any_instance_of(Devise::Controllers::Helpers).to receive(:user_signed_in?).and_return(false)
end
The code is not executed because the user is probably not signed in.
To sign in a devise user in rspec, have this in spec/support/controller_macros.rb.
module ControllerMacros
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryGirl.create(:user)
user.confirm! #if you are using the "confirmable" module
sign_in user
end
end
end
In spec/rails_helper.rb have this:
RSpec.configure do |config|
config.include Devise::Test::ControllerHelpers, :type => :controller
config.extend ControllerMacros, :type => :controller
end
Then in your controller spec:
describe MyController do
login_user
....
end

rspec fails where 'current_user' code written in controller

I have started rspec coding recently and i am new to rails framework, rspec fails where i am using 'current_user' in controller. Please check below for my controller and rspec code. Thanks in advance.
Controller code:
def task
#tasks = current_user.alerts.where(kind: "TASK")
end
rspec code:
describe "get #task" do
it "assigns a task" do
sign_in(#user)
get :task
expect(response).to have_http_status(200)
end
You can do it like this:
#request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryGirl.create(:user) # Don't forget to create a factory for user
user.confirm! # or set a confirmed_at inside the factory. Only necessary if you are using the "confirmable" module
sign_in user
It is better to put it in support/devise.rb:
module ControllerMacros
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryGirl.create(:user)
user.confirm! # or set a confirmed_at inside the factory. Only necessary if you are using the "confirmable" module
sign_in user
end
end
end
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
config.extend ControllerMacros, :type => :controller
end
you can say login_user instead of sign_in(#user)

Testing views that use CanCan and Devise with RSpec

I was trying to test a simple index view, which has following code inside:
- if can? :destroy, MyModel
%th Options
MyModelsController has following options (Inherited Resources + CanCan + Devise):
class MyModelsController < ApplicationController
inherit_resources
nested_belongs_to :mymodel
before_filter :authenticate_user!
load_and_authorize_resource :project
load_and_authorize_resource :mymodel, :through => :project
When running specs, it crashes at the line - if can? :destroy, MyModel
Failure/Error: render
ActionView::Template::Error:
undefined method `authenticate' for nil:NilClass
There's no traceback, nothing to base on...
I thought that maybe I'm not authorized and signed when testing views, but Devise::TestHelpers should only be included in controller tests (and that's how I have it).
I was trying to override method can? in both Ability and the controller, but that gave no effect.
This is described in the CanCan docs for controller testing, and can also be modified to apply to view specs. Here's one way to do it:
require 'spec_helper'
describe "mymodel/index.html.erb" do
before(:each) do
assign(:my_model,mock_model(MyModel))
#ability = Object.new
#ability.extend(CanCan::Ability)
controller.stub(:current_ability) { #ability }
end
context "authorized user" do
it "can see the table header" do
#ability.can :destroy, MyModel
render
rendered.should have_selector('th:contains("Options")')
end
end
context "unauthorized user" do
it "cannot see the table header" do
render
rendered.should_not have_selector('th:contains("Options")')
end
end
end
The 'before :each' code posted by zetetic doesn't work for me. My views bork on the 'can?' method because 'current_ability' in the view returns nil. I fixed it by using this 'before :each' code instead:
#ability = Ability.new(user)
assign(:current_ability, #ability)
controller.stub(:current_user, user)
view.stub(:current_user, user)
The above code simulates a login.
In your spec_helper:
config.include Devise::TestHelpers, :type => :view
In your view spec:
controller.stub!(current_user: [some user])
view.stub!(current_user: [some user])
For new RSpec 3.0 syntax
before(:each) do
assign(:my_model,mock_model(MyModel))
#ability = Object.new.extend(CanCan::Ability)
allow(controller).to receive(:current_ability).and_return(#ability)
end
The problem with the solution from the CanCan wiki is that it requires a #ability. can ... in each example, which doesn't feel very DRY.
Moreover, it doesn't actually stub out the abilities themselves, but the method that returns the controller's ability. The ability is not a stub and consequently the abilities are checked.
If you're using Rspec and want to test just the controller (and not it's abilities), here's how to stub it out:
before(:each) do
ability = mock(:ability).as_null_object
controller.stub(:current_ability).and_return(ability)
end
This works because as_null_object returns truthy values for all methods, so the ability checking methods pass.
Based on John Kloian's example I defined this useful helper:
# spec/support/sign_in.rb
module ViewSpecSignInHelper
def login_as(user)
allow(view).to receive(:signed_in?).and_return true
allow(controller).to receive(:current_user).and_return user
end
end
RSpec.configure do |config|
config.include ViewSpecSignInHelper, type: :view
end
My full spec/support/sign_in.rb looks like this:
module ControllerSpecSignInHelper
def login_as(user)
sign_in(user)
end
end
module FeatureSpecSignInHelper
# See https://github.com/plataformatec/devise/wiki/How-To%3a-Test-with-Capybara
include Warden::Test::Helpers
Warden.test_mode!
# A login_as(user) method is provided already!
end
module ViewSpecSignInHelper
def login_as(user)
allow(view).to receive(:signed_in?).and_return true
allow(controller).to receive(:current_user).and_return user
end
end
RSpec.configure do |config|
config.include Devise::Test::ControllerHelpers, type: :controller
config.include Devise::Test::ControllerHelpers, type: :view
config.include ControllerSpecSignInHelper, type: :controller
config.include FeatureSpecSignInHelper, type: :feature
config.include ViewSpecSignInHelper, type: :view
end
I can now login a user the same way in feature, controller, and view specs:
user = create :user # Using FactoryBot
login_as user

Resources