So I want to test controller which is using devise.
require 'rails_helper'
describe ArticlesController do
before(:all) { #article = FactoryGirl.create(:article) }
sign_in_admin
describe 'GET new' do
let(:call_request) { get :new }
before { call_request }
context 'admin signed in' do
it { is_expected.to respond_with :ok }
end
context 'admin signed out' do
it { is_expected.to respond_with 302 }
end
end
end
I followed this guide from devise wiki. So I created ControllerMacros module as you can see here:
module ControllerMacros
def sign_in_admin
before(:each) do
#request.env['devise.mapping'] = Devise.mappings[:admin]
#admin = FactoryGirl.create(:admin)
sign_in #admin
end
end
end
And included it, and other needed things to rails_helper:
require_relative 'support/controller_macros.rb'
RSpec.configure do |config|
config.include Devise::TestHelpers, type: :controller
config.extend ControllerMacros, type: :controller
(...)
Here is admin factory:
FactoryGirl.define do
factory :admin do
email 'email#email.com'
password 'password'
end
end
But now I don't know how to sign out admin? As sign_in_admin is executing before each test, but I need to sign admin out for some of the tests. What is the best way to test this controller with signed in/out admin?
btw I'm kinda new to testing
Devise provides some helper methods for testing, including login_user which takes a user, so you shouldn't need to create the sign_in_admin function. Also by using before(:all) at the top of the block, you've signed in the admin for the entire suite of specs.
Use before(:each) within the individual contexts that you want a signed_in_admin.
describe ArticlesController do
describe 'GET new' do
let(:call_request) { get :new }
let(:admin_user) { FactoryGirl.create(:admin) }
before { call_request }
context 'admin signed in' do
sign_in(admin_user)
it { is_expected.to respond_with :ok }
end
context 'admin not signed in' do
it { is_expected.to respond_with 302 }
end
end
end
Better way is to close before action into a context. The contexts are signed_in, and non-singed in. So you can do something as follows:
describe 'GET new' do
let(:call_request) { get :new }
before { call_request }
context "signed in" do
before { sign_in_admin }
it { is_expected.to respond_with :ok }
end
context 'non-singed in' do
it { is_expected.to respond_with 302 }
end
end
Related
I'm facing the problem with logging in rspec with selected user. I've tried making controller module like that:
module ControllerMacros
def login(user)
before(:each) do
#request.env['devise.mapping'] = Devise.mappings[:user]
payload = { jti: SecureRandom.uuid, sub: user.id.to_s }
cookies['access_token'] = JWT.encode(payload, ENV['DEVISE_JWT_SECRET_KEY'], 'HS256')
sign_in user
end
end
end
The issue is that I'm either unbale to pass user in situation like that:
context 'as admin' do
let(:user) { create :user, :super_admin }
login user
before do
get :index
end
it { expect(response).to be_ok }
end
and i get:
Or if I try something like that:
context 'as admin' do
let(:user) { create :user, :super_admin }
before do
login user
get :index
end
it { expect(response).to be_ok }
end
I get:
How can I make it work?
you have created login helper with before(:each) block
In the first case, where you are calling login helper outside the it or before block and passing user instance created using let. here user is not available as scope of let variables is inside the it or before block.
In the second case, where you are calling login helper inside the before block, but login helper also adds before(:each) block. I suspect due to calling of before(:each) within before it raises the error. similar issue reported here
Possible solutions
create user inside the login helper and call it outside the it block
module ControllerMacros
def login
before(:each) do
user = create :user, :super_admin
#request.env['devise.mapping'] = Devise.mappings[:user]
payload = { jti: SecureRandom.uuid, sub: user.id.to_s }
cookies['access_token'] = JWT.encode(payload, ENV['DEVISE_JWT_SECRET_KEY'], 'HS256')
sign_in user
end
end
end
context 'as admin' do
login
before do
get :index
end
it { expect(response).to be_ok }
end
Remove before(:each) block from login helper
module ControllerMacros
def login(user)
#request.env['devise.mapping'] = Devise.mappings[:user]
payload = { jti: SecureRandom.uuid, sub: user.id.to_s }
cookies['access_token'] = JWT.encode(payload, ENV['DEVISE_JWT_SECRET_KEY'], 'HS256')
sign_in user
end
end
context 'as admin' do
let(:user) { create :user, :super_admin }
before do
login user
get :index
end
it { expect(response).to be_ok }
end
I've got custom member_action in my Active Admin panel which is responsible for resending devise reset password instructions.
admin/users.rb
ActiveAdmin.register User do
member_action :reset_password do
user = User.find(params[:id])
user.send_reset_password_instructions
redirect_to(admin_user_path(user),
notice: "Password reset email sent to #{user.email}")
end
end
How to write RSpec tests for such an action? The only thing I found is this one and I think it's not quite related to my problem.
I was trying to sth like below:
require 'rails_helper'
describe Admin::UsersController, type: :controller do
include Devise::TestHelpers
let!(:admin) { create(:admin_user) }
before(:each) do
sign_in admin
end
describe 'GET user' do
let(:user) { create(:user, :random_email) }
before(:each) do
User.should_receive(:find).at_least(:once).and_return(user)
get :show
end
it 'sends email' do
get :reset_password
expect(user).should_receive(:send_reset_password_instructions)
end
end
end
But I'm getting an error:
ActionController::UrlGenerationError:
No route matches {:action=>"reset_password", :controller=>"admin/users"}
Personally I prefer to use a feature test, since when using active admin, UI stuff handle by the framework:
RSpec.feature 'Reset Password', type: :feature do
let(:user) { create :user }
before do
login_as(user, scope: :user)
end
scenario 'can delete future episode' do
visit some_path
click_link 'Reset Password'
expect(page.current_path).to eq(admin_user_path(user))
expect(page).to have_content("Password reset email sent to #{user.email}")
end
end
Ok, it turns out small adjustments (pass the user.id in params) make the trick.
describe Admin::UsersController, type: :controller do
include Devise::Test::ControllerHelpers
before { sign_in admin }
let!(:admin) { create(:admin_user) }
describe 'GET user' do
let(:user) { create(:user, :random_email) }
before do
allow(User).to receive(:find).at_least(:once) { user }
get :show, params: { id: user.id }
end
it 'sends email' do
get :reset_password, params: { id: user.id }
expect(flash[:notice]).to match("Password reset email sent to #{user.email}")
end
end
end
I have a custom AutorizationAdapter that I would like to test using RSpec:
class AdminAuthorization < ActiveAdmin::AuthorizationAdapter
def authorized?(_action, _subject = nil)
user.admin?
end
end
Initially I used a custom method but since I'm using Devise, using a custom AuthorizationAdapter seemed to be the way to go.
How would you go about testing it ? I tought one way to test it is to create a request spec for one of the controller and test for status code & redirection, something like that:
require 'rails_helper'
RSpec.describe 'AdminUsers', type: :request do
describe 'GET /admin_users' do
context 'admin' do
let(:admin_user) { create(:admin_user) }
before { sign_in super_user }
get admin_users_path
expect(response).to have_http_status(200)
end
context 'non admin' do
let(:user) { create(:user) }
before { sign_in user }
it 'redirects to the login page' do
get admin_users_path
expect(response).to have_http_status(302)
expect(response).to redirected_to '/admin/login'
end
end
context 'non logged in user' do
it 'redirects to the login page' do
get admin_users_path
expect(response).to have_http_status(302)
expect(response).to redirected_to '/admin/login'
end
end
end
end
I'm not sure this is the way to go.
These look reasonable to me. You can also look at the unit and feature specs that are in the ActiveAdmin test suite. However, AuthorizationAdapter itself is a PORO so you should be able to unit test in isolation: in the example given above that would be a fairly trivial test.
I have this pretty basic helper which relies on current_user variable provided by Sorcery in controllers and helpers
def current_user_link
user_link current_user
end
def user_link(user, html_options = {}, &block)
link_to user.to_s, user, html_options, &block
end
How can I test this helper?
describe UsersHelper do
describe '#current_user_link' do
it 'should return a link to the current user' do
expected_link = link_to current_user.name, current_user
???
expect(current_user_link).to eq expected_link
end
end
Do I need to stub current_user somehow?
Is it even worth testing?
This is how I solved it.
describe '#current_user_link' do
it 'returns a link to the current user ' do
user = build(:user)
expected_link = link_to user.name, user
allow(controller).to receive(:current_user).and_return(user)
expect(helper.current_user_link).to eq(expected_link)
end
end
PSA: dont forget to call your method on helper.
I was trying to use current_user with Sorcery in an Rspec ApplicationHelper spec and none of the above answers worked for me.
What worked for me was first defining a user with FactoryGirl:
let(:user) { create(:user) }
Then, write an example like this:
it "does stuff" do
allow(helper).to receive(:current_user).and_return(user)
expect(helper.some_method_using_current_user).to do_something
end
Key difference is using the helper object in the example.
simpliest work around is to declare in spec:
let(:current_user) { create(:user) }
you can stub your current_user
describe UsersHelper do
describe '#current_user_link' do
let(:user) { FactoryGirl.build(:user) }
let(:expected_link) { link_to user.name, user }
before { allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) }
it { expect(current_user_link).to eq(expected_link) }
end
end
or set your user to session
than you should
let(:user) { FactoryGirl.create(:user) }
and
before { allow_any_instance_of(ActionDispatch::Request).to receive(:session).and_return(user_id: user.id) }
This worked for me:
describe UsersHelper do
describe '#current_user_link' do
it 'should return a link to the current user' do
user = FactoryGirl.create(:user)
allow_any_instance_of(UsersHelper).to receive(:current_user).and_return(user)
expected_link = link_to user.name, user
expect(current_user_link).to eq(expected_link)
end
end
end
In the rails_helper.rb you need to have:
RSpec.configure do |config|
config.include Devise::Test::ControllerHelpers, type: :helper
end
When testing helper modules with RSpec, you need to stub the method in your Rspec::ExampleGroups target...
allow_any_instance_of(RSpec::ExampleGroups::UsersHelper).to receive(:current_user).and_return user
For those who came from Devise:
You can simply define the method inside the spec.
describe 'option_for_product_weight' do
before {
def helper.current_user
User.first
end
}
subject { helper.option_for_product_weight }
it 'returns the list' do
expect(subject).not_to be_empty
end
end
I am using Devise for my user logins and stuff and rspec for testing. I have looked at the Devise testing guide for rspec and mixined ControllerMicros to controller specs.
And actually things are all working fine if I have tests organized like this:
describe 'GET #index' do
context 'user logged in but not admin' do
login_user
it 'should redirect to root_path for non_user' do
get :index
// I have asserted that the current_user here is not nil
expect(response).to redirect_to(root_path)
end
end
end
However, if I have 2 tests in the context and I got current_user is nil for the non-first test.
describe 'GET #index' do
context 'user logged in but not admin' do
login_user
it 'should redirect to root_path for non_user' do
get :index
// I have asserted that the current_user here is not nil
expect(response).to redirect_to(root_path)
end
it 'should do some other thing' do
get :index
// the current_user method returns nil here
expect(response).to redirect_to(root_path)
end
end
end
And the worst part is that it seems this problem is not deterministic: happens somewhat randomly--cause after several failed runs the suite just passed on my computer(but still fails on Travis my build)
Some additional information:
the ControllerMacro.rb
module ControllerMacros
def login_admin
before(:each) do
# #request.env["devise.mapping"] = Devise.mappings[:user]
user = User.find_by(email: 'default_admin#controller.spec')
user ||= FactoryGirl.create(:user, email: 'default_admin#controller.spec', uid: 'default_admin.controller.spec')
admin = Admin.find_by(user_id: user.id)
FactoryGirl.create(:admin, user: user) if not admin
sign_in user
end
end
def login_user(user = nil)
before(:each) do
# #request.env["devise.mapping"] = Devise.mappings[:user]
user ||= User.find_by(email: 'default_user#controller.spec')
user ||= FactoryGirl.create(:user, email: 'default_user#controller.spec', uid: 'default_user.controller.spec')
sign_in user
end
end
end
the rails_helper.rb
RSpec.configure do |config|
# for loading devise in test
config.include Devise::TestHelpers, :type => :controller
config.extend ControllerMacros, :type => :controller
end
Your login_user method is run when the test suite load, you should put it in a before :each block to run it once for each test.
describe "GET index" do
before do
login_user
end
it 'blabla' do
get :index
expect(response).to redirect_to(root_path)
end
end
PS : Don't know what you do in your login_user method, but Devise have some nice helpers you can include as follow
#rails_helper.rb
RSpec.configure do |config|
config.include Devise::TestHelpers, type: :controller
end
#then in you test
before do
sign_in user_instance
end
UPDATE from comment
If you have multiple type of user / devise login entry, maybe try to specify the devise mapping you're trying to sign in the user to , as follow :
sign_in :user, user_instance
sign_in :admin, admin_user_instance