I have a helper method that returns one link if user_signed_in?. I'm using Devise 2.2.8, Rails 3.2.2 and Rspec 3.4.0.
application_helper.rb
def session_button
if user_signed_in?
link_to "Restricted Area", user_index_path
else
link_to "Login", new_user_session_path
end
end
And my test:
application_helper_spec.rb
describe "#session_button" do
context "user signed in" do
login_user
it "returns a link to Restricted Area" do
expect(session_button).to include "Área Restrita"
expect(session_button).to include user_index_path
end
end
context "user not signed in" do
it "returns a link to New Session" do
expect(session_button).to include "Login"
expect(session_button).to include new_user_session_path
end
end
end
login_user is a macro included in my tests. I also loaded TestHelpers.
RSpec.configure do |config|
config.include Devise::TestHelpers, type: :helper
config.extend ControllerMacros, :type => :helper
end
The problem: I'm getting this error:
1) ApplicationHelper#session_button user signed in returns a link to Restricted Area
Failure/Error: if user_signed_in?
NoMethodError:
undefined method `user_signed_in?' for #<RSpec::ExampleGroups::ApplicationHelper::SessionButton::UserSignedIn:0x0000000795a608>
I also tried to stub the method
allow(helper).to receive(:user_signed_in?).and_return(true)
but I get the same error.
How do I stub this devise helper?
I found a way using anonymous helper but I don't know if there is a better way...
RSpec.describe ApplicationHelper, :type => :helper do
helper do
def user_signed_in?
user.present?
end
end
describe "#session_button" do
context "user signed in" do
let(:user) { FactoryGirl.create :user }
it "returns a link to Restricted Area" do
expect(session_button).to include "Área Restrita"
expect(session_button).to include user_index_path
end
end
context "user not signed in" do
let(:user) { nil }
it "returns a link to New Session" do
expect(session_button).to include "Login"
expect(session_button).to include new_user_session_path
end
end
end
end
Related
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
I am getting a uninitialized constant ControllerMacros (NameError), perhaps similar to these issues (1, 2, 3). I must be screwing up the syntax while trying to include controller macros so I can login with devise and pass controller tests in rspec. Link to GitHub repo.
Rails 4.1.8 and Ruby 2.1.2
spec/controllers/static_pages_controller_spec.rb
require 'rails_helper'
describe StaticPagesController, :type => :controller do
describe "GET #index" do
it "responds successfully with an HTTP 200 status code" do
login_user
get :index
expect(response).to be_success
expect(response).to have_http_status(200)
end
it "renders the index template" do
login_user
get :root
expect(response).to render_template("index")
end
end
end
spec/support/controller_macros.rb
module ControllerMacros
def login_admin
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:admin]
admin = FactoryGirl.create(:admin)
sign_in :user, admin # sign_in(scope, resource)
end
end
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
added lines to spec/rails_helper
#helps avoid authentication error during rspec:
config.include Devise::TestHelpers, :type => :controller
config.include ControllerMacros, :type => :controller
This worked for me.
spec/support/devise.rb
require 'devise'
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
config.extend ControllerMacros, :type => :controller
end
Also make sure this line is uncommented in rails_helper.rb
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
I looks like you may need to add require 'support/controller_macros' to the top of your rails_helper.rb file. This directory would not be included by default with RSpec.
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)
In the devise documentation they give tips on how you can have access to current_user when testing a controller:
https://github.com/plataformatec/devise/wiki/How-To:-Test-controllers-with-Rails-3-and-4-%28and-RSpec%29
However, what about when doing a feature test? I am trying to test a create method of one of my controllers, and in that controller is used the current_user variable.
The problem is that the macro suggested in devise uses the #request variable, and it is nil for a feature spec. What is a workaround?
EDIT:
This is what I have so far for my current spec:
feature 'As a user I manage the orders of the system' do
scenario 'User is logged in ad an admin' do
user = create(:user)
order = create(:order, user: user)
visit orders_path
#Expectations
end
end
The problem is that in my OrdersController I have a current_user.orders call, and since current_user is not defined, it will redirect me to /users/sign_in.
I have defined this under /spec/features/manage_orders.rb
from https://github.com/plataformatec/devise/wiki/How-To:-Test-controllers-with-Rails-3-and-4-%28and-RSpec%29
if i have understood you right, maybe you need to use
subject.current_user.email
#or
controller.current_user.email
for example :
describe OrdersController, :type => :controller do
login_user
describe "POST 'create'" do
it "with valid parametres" do
post 'create', title: 'example order', email: subject.current_user.email
end
end
end
controller_macros.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
Don't forget to include this into your spec_helper.rb :
config.include Devise::TestHelpers, type: :controller
config.extend ControllerMacros, type: :controller
Here's what I think you are looking for:
require 'spec_helper'
include Warden::Test::Helpers
Warden.test_mode!
feature 'As a user I manage the orders of the system' do
scenario 'User is logged in ad an admin' do
user = create(:user)
login_as(user, scope: :user)
order = create(:order, user: user)
visit orders_path
#Expectations
end
end
you can define login_user as a method for the user to login as follows (put it in support folder):
def login_user
Warden.test_mode!
user = create(:user)
login_as user, :scope => :user
user.confirmed_at = Time.now
user.confirm!
user.save
user
end
Then in the scenario say:
user = login_user
I'm new to RSpec and I'm just wondering how to reuse a context across several actions in a controller. Specifically, I have code like this:
describe "GET index" do
context "when authorized" do
...
end
context "when unauthorized" do
it "denys access"
end
end
describe "GET show" do
context "when authorized" do
...
end
context "when unauthorized" do
it "denys access"
end
end
...
And I'd like to DRY it up a bit. The unauthorized context is the same on every action, how can I reuse it?
Shared examples are your friend:
Create a new file, something like spec/shared/unauthorized.rb and include it in your spec_helper then format it like this:
shared_examples_for "unauthorized" do
context "when unauthorized" do
it "denys access"
end
end
Then in your specs:
include_examples "unauthorized"
Do that in each describe block and you should be golden.
if you use popular gem Devise, you can reuse devise mapping like this:
require "spec_helper"
describe Worksheet::CompanyController do
login_user_admin #<= this macros on /spec/support/controller_macros.rb
describe '#create' do
it 'admin login and create worksheet' do
post :create, worksheet_company: attributes_for(:"Worksheet::Company")
expect(response.status).to eq(302)
expect(response).to redirect_to(admin_root_path)
end
end
create and login admin_user
spec/support/controller_macros.rb
module ControllerMacros
def login_user_admin
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user_admin]
user_admin = FactoryGirl.create(:user_admin)
user_admin.confirm!
sign_in user_admin
end
end
end
on spec/spec_helper.rb:
RSpec.configure do |config|
...
config.include Devise::TestHelpers, type: :controller
config.extend ControllerMacros, type: :controller
...
end