I have a controller based on MHartl's RoR4 Tutorial
And just like MHartl, I'm not using Devise, I rolled my own authentication system
Having trouble with the RSpec for UsersController#Edit since the view has a call to current_user.admin? and the controller calls #path_switch = path_switch
I keep getting RSpec errors along the lines of:
1) User Pages Edit
Failure/Error: visit edit_user_path(user)
NoMethodError:
undefined method `admin?' for nil:NilClass
# ./app/controllers/users_controller.rb:106:in `path_switch'
# ./app/controllers/users_controller.rb:53:in `edit'
# ./spec/requests/user_pages_spec.rb:54:in `block (3 levels) in <top (required)>'
UsersController:
class UsersController < ApplicationController
...
def edit
#user = User.find(params[:id])
#path_switch ||= path_switch #error
end
...
def path_switch
if current_user.admin? #error
users_path
else
root_path
end
end
end
I found this really helpful article that gives me hope that I'm on the right track, but I can't get it to work.
Here's as far as I've gotten (updated):
user_pages_spec.rb:
require 'spec_helper'
require 'support/utilities'
describe "User Pages" do
#include SessionsHelper
let(:user) { FactoryGirl.create(:user) }
let(:current_user) {user}
subject { page }
describe "Edit" do
before do
sign_in(user)
visit edit_user_path(user)
end
it '(check links and content)' do
should have_button('Submit')
should have_link('Cancel')
should have_content(user.fname+"\'s profile")
end
...
end
...
end
But current_user is still coming back nil
Any help/guidance is appreciated. Thanks!
Adding include SessionsHelper to the top describe block of my user_pages_edit.rb seems to try and use the sign_in(path) from that helper. Creating an issue between RSpec and cookies.permanent. So that's a bust.
unfortunately, this brings me right back to my .admin? error.
There are two calls to current_user.admin?
One is in the controller:
def path_switch
if current_user.admin? #error current_user == nil
users_path
else
root_path
end
end
One is in the view as ERB:
<% if current_user.admin? %>
<div class="row col-xs-6 col-sm-6 col-md-3">
<div class="input-group input-selector">
...
All I need to do is figure out how to set current_user.admin = true and pass it to the controller (and then hopefully the view) so that the page can load.
To do that, all I need to do is set current_user = user because user.admin == true.
If you are doing unit testing of your controller you can simply stub your current_user in a before block, like this:
let(:user) { ... }
# RSpec version <= 2 syntax:
before { controller.stub(:current_user) { user } }
# RSpec version >= 3 syntax:
before { allow(controller).to receive(:current_user) { user } }
If you are doing feature or request testing, I advise you to perform a real log-in by creating a user in your database, then passing through your log-in page with this user credentials
Here you seem to be doing a feature test, you should write a helper that perform the creation of the user record and go through the log-in.
Additionally in feature testing to gain a lot of time while running your test, do not hesitate to group your assertions in the same block. Clearly, instead of:
it { should have_button('Submit')}
it { should have_link('Cancel')}
it { should have_content(user.fname+"\'s profile")}
You can write
it 'check links and content' do
should have_button('Submit')
should have_link('Cancel')
should have_content(user.fname+"\'s profile")
end
That will avoid to generate several session of your feature environment, and also to log-in several times
Also works
user = create(:user) #FactoryBot
allow(controller).to receive(:current_user).and_return(user)
For me, worked with:
before { controller.stub!(:current_user).and_return(user) }
I run into the same problem with a legacy Rails 4 app and based my solution on this test case of Rspec Views.
First define a helper that will define the missing helper method in the controller instance
# /spec/support/concerns/view_helper.rb
module ViewHelper
def include_current_user_helper(&block)
controller.singleton_class.class_eval do
define_method :current_user, &block
helper_method :current_user
end
end
end
Then configure Rspec to include it in all the view helpers
# spec/rails_helper.rb
# ...
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
RSpec.configure do |config|
# ...
config.include ViewHelper, type: :view
end
And in the view specs it is called like this
RSpec.describe 'something' do
let!(:user) { FactoryGirl.create(:user) } # Note the "!" there
before { include_current_user_helper { user } }
# do stuff
end
Note: the call to let with bang is important as content inside the block will be executed lazily, outside of the test scope, and user will be nill if not
Related
I have a controller that depends on the user being authenticated. So it looks like this
class PlansController < ApplicationController
before_action :authenticate_user!
def create
puts "here"
if user_signed_in?
puts "true"
else
puts "false"
end
end
end
My controller tests are working just fine when teh user IS signed in, i.e., when I'm writing something like this:
require 'rails_helper'
require 'devise'
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
end
describe "create action" do
before do
#user = User.create(...)
sign_in :user, #user
end
it "should puts here and then true" do
post :create
# => here
# => true
end
end
But I'd also like to test what happens in the else statement. Not sure how to do this, it fundamentally doesn't even put the here. Is it possible to test this? Or should I just leave and let Devise be?
describe "create action" do
before do
#user = User.create(...)
# do not sign in user (note I have also tried to do a sign_in and then sign_out, same result)
end
it "should puts here and then true" do
post :create
# => nothing is put, not even the first here!
# => no real "error" either, just a test failure
end
end
The before_action :authenticate_user! will immediately redirect you to the default sign-in page, if the user isn't signed in, skipping the create action altogether.
The if user_signed_in? statement is moot in this case, because the user will always be signed in when that code has the chance to run.
If plans can be created with or without an authenticated user, remove the before_action line.
I've spent far too long messing with this before asking for help. I can't seem to get RSpec and Sorcery to play together nicely. I've read through the docs on Integration testing with Sorcery and can post the login action properly, but my tests still doesn't think the user is logged in.
# spec/controllers/user_controller_spec
describe 'user access' do
let (:user) { create(:user) }
before :each do
login_user(user[:email], user[:password])
end
it "should log in the user" do
controller.should be_logged_in
end
end
And my login_user method
# spec/support/sorcery_login
module Sorcery
module TestHelpers
module Rails
def login_user email, password
page.driver.post(sessions_path, { email: email , password: password, remember_me: false })
end
end
end
end
The sessions controller handles the pages properly when I use them on the generated pages just fine. I tried outputting the results of the login_user method and it appears to properly post the data. How do I persist this logged in user through the tests? Does a before :each block not work for this? I'm just not sure where it could be running wrong and I'm pretty new to testing/RSpec so I may be missing something obvious. I'd appreciate any help.
Here's the output of the failed tests:
1) UsersController user access should log in the user
Failure/Error: controller.should be_logged_in
expected logged_in? to return true, got false
I just went through this yesterday. Here's what I did, if it helps.
Sorcery provides a test helper login_user that relies on a #controller object being available. This works great in controller specs, but doesn't work in integration tests. So the workaround in integration tests is to write another method (like the one you have above) to simulate actually logging in via an HTTP request (essentially simulating submitting a form).
So my first thought is that you should try renaming your method to login_user_post or something else that doesn't collide with the built-in test helper.
Another potential gotcha is that it looks to me like the Sorcery helper assumes that your user's password is 'secret'.
Here's a link to the built-in helper so you can see what I'm talking about:
https://github.com/NoamB/sorcery/blob/master/lib/sorcery/test_helpers/rails.rb
Good luck - I really like this gem except for this part. It is really only fully explained by patching together SO posts. Here's the code I use:
Integration Helper
module Sorcery
module TestHelpers
module Rails
def login_user_post(user, password)
page.driver.post(sessions_url, { username: user, password: password})
end
def logout_user_get
page.driver.get(logout_url)
end
end
end
end
Integration Spec (where user needs to be logged in to do stuff)
before(:each) do
#user = create(:user)
login_user_post(#user.username, 'secret')
end
Controller Spec (where the regular login_user helper works fine)
before(:each) do
#user = create(:user)
login_user
end
Note that login_user doesn't need any arguments if you have an #user object with the password 'secret'.
Did you try adding to spec/spec_helpers.
RSpec.configure do |config|
# ...
config.include Sorcery::TestHelpers::Rails::Controller
end
Nota that you need to include Sorcery::TestHelpers::Rails::Controller, not just Sorcery::TestHelpers::Rails.
Then you will be able to login_user from any controller specs like:
describe CategoriesController do
before do
#user = FactoryGirl::create(:user)
end
describe "GET 'index'" do
it "returns http success" do
login_user
get 'index'
expect(response).to be_success
end
end
end
The way you pass a password is probably wrong. It may be encrypted at this point. In provided example I will try to do this at first:
describe 'user access' do
let (:user) { create(:user, password: 'secret') }
before :each do
login_user(user[:email], 'secret')
end
it "should log in the user" do
controller.should be_logged_in
end
end
This seems to be very poorly documented. The above solutions did not work for me. Here's how I got it to work:
Check your sessions_url. Make sure it is correct. Also, check what params are necessary to log in. It may be email, username, etc.
module Sorcery
module TestHelpers
module Rails
def login_user_post(email, password)
page.driver.post(sessions_url, { email:email, password: password })
end
end
end
end
RSpec config:
config.include Sorcery::TestHelpers::Rails
Spec helper:
def app
Capybara.app
end
spec/controllers/protected_resource_spec.rb:
describe UsersController do
before do
# Create user
# Login
response = login_user_post( user.email, :admin_password )
expect( response.headers[ 'location' ]).to eq 'http://test.host/'
# I test for login success here. Failure redirects to /sign_in.
#cookie = response.headers[ 'Set-Cookie' ]
end
specify 'Gets protected resource' do
get protected_resource, {}, { cookie:#cookie }
expect( last_response.status ).to eq 200
end
I am new to programming, this is my first application.
While creating an application in Rails, i have two models. User&list,nested.
resources :users do
resources :lists
end
These are the following routes i obtain with this setting:
user_lists GET /users/:user_id/lists(.:format) lists#index
POST /users/:user_id/lists(.:format) lists#create
new_user_list GET /users/:user_id/lists/new(.:format) lists#new
edit_user_list GET /users/:user_id/lists/:id/edit(.:format)lists#edit
user_list GET /users/:user_id/lists/:id(.:format) lists#show
PUT /users/:user_id/lists/:id(.:format) lists#update
DELETE /users/:user_id/lists/:id(.:format) lists#destroy
With regards i have created the views with the following links.
<div class="stats">
<a href="<%= user_lists_path(current_user) %>">
<%= pluralize(current_user.lists.count, 'List') %>
</a>
</div>
<div class="list">
<%= link_to 'Create List', new_user_list_path(current_user) %>
</div>
These work as expected, however when i use the same url helpers in testing i get an error.
describe "List create page" do
let(:user) { FactoryGirl.create(:user) }
before do
user.save
visit new_user_list_path(user)
end
it { should have_selector('title', text: 'SocialTask | List') }
it { should have_selector('h1', text: 'Create list') }
describe "invalid list creation" do
before { click_button 'Create list' }
it { should have_content('Error in creating list') }
end
end
This causes the tests to have an error.
Failure/Error: visit new_user_list_path(user)
NoMethodError:
undefined method `lists' for nil:NilClass
I have tried playing around with the url that did not work.
I tried updating rspec/capybara that did not work either.
I have also checked the inclusion of
config.include Rails.application.routes.url_helpers
in the spec helper.
How do i get the helpers to work? Or am i missing some minor detail?
Thanks in advance.
Helper Methods.
module SessionsHelper
def sign_in(user)
cookies.permanent[:remember_token] = user.remember_token
self.current_user = user
end
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
def signed_in?
!current_user.nil?
end
def sign_out
self.current_user = nil
cookies.delete(:remember_token)
end
def current_user?(user)
current_user == user
end
end
The rspec helper to sign in.
support/utilities.rb
include ApplicationHelper
def sign_in(user)
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
cookies[:remember_token] = user.remember_token
end
Without seeing the stack trace, I think your problem is in the view on this line:
<%= pluralize(current_user.lists.count, 'List') %>
It seems like current_user is nil. Normally you should define some kind of helper method in your RSpec suite to simulate a user logging in. That way, current_user will return the user that you stub out in the test.
Here's an example:
# spec/support/session_helper.rb
module SessionHelper
def login(username = 'admin')
request.session[:user_id] = User.find_by_username(username).id
end
end
Yours will differ depending on how you authenticate your users. For example, Devise publishes its own set of test helpers, so you can simply include its helpers directly:
# spec/support/devise.rb
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
end
Seems it's getting an error because the user doesn't exists. Try to change user.save to user.save! then you'll catch the error on creation I think..
I have been playing with Rails for a couple of years now and have produced a couple of passable apps that are in production. I've always avoided doing any testing though and I have decided to rectify that. I'm trying to write some tests for an app that I wrote for work that is already up and running but undergoing constant revision. I'm concerned that any changes will break things so I want to get some tests up and running. I've read the RSpec book, watched a few screencasts but am struggling to get started (it strikes me as the sort of thing you only understand once you've actually done it).
I'm trying to write what should be a simple test of my ReportsController. The problem with my app is that pretty much the entire thing sits behind an authentication layer. Nothing works if you're not logged in so I have to simulate a login before I can even send forth a simple get request (although I guess I should write some tests to make sure that nothing works without a login - I'll get to that later).
I've set up a testing environment with RSpec, Capybara, FactoryGirl and Guard (wasn't sure which tools to use so used Railscasts' suggestions). The way I've gone about writing my test so far is to create a user in FactoryGirl like so;
FactoryGirl.define do
sequence(:email) {|n| "user#{n}#example.com"}
sequence(:login) {|n| "user#{n}"}
factory :user do
email {FactoryGirl.generate :email}
login {FactoryGirl.generate :login}
password "abc"
admin false
first_name "Bob"
last_name "Bobson"
end
end
and then write my test like so;
require 'spec_helper'
describe ReportsController do
describe "GET 'index'" do
it "should be successful" do
user = Factory(:user)
visit login_path
fill_in "login", :with => user.login
fill_in "password", :with => user.password
click_button "Log in"
get 'index'
response.should be_success
end
end
end
This fails like so;
1) ReportsController GET 'index' should be successful
Failure/Error: response.should be_success
expected success? to return true, got false
# ./spec/controllers/reports_controller_spec.rb:13:in `block (3 levels) in <top (required)>'
Interestingly if I change my test to response.should be_redirect, the test passes which suggests to me that everything is working up until that point but the login is not being recognised.
So my question is what do I have to do to make this login work. Do I need to create a user in the database that matches the FactoryGirl credentials? If so, what is the point of FactoryGirl here (and should I even be using it)? How do I go about creating this fake user in the testing environment? My authentication system is a very simple self-made one (based on Railscasts episode 250). This logging in behaviour will presumably have to replicated for almost all of my tests so how do I go about doing it once in my code and having it apply everywhere?
I realise this is a big question so I thank you for having a look.
The answer depends on your authentication implementation. Normally, when a user logs in, you'll set a session variable to remember that user, something like session[:user_id]. Your controllers will check for a login in a before_filter and redirect if no such session variable exists. I assume you're already doing something like this.
To get this working in your tests, you have to manually insert the user information into the session. Here's part of what we use at work:
# spec/support/spec_test_helper.rb
module SpecTestHelper
def login_admin
login(:admin)
end
def login(user)
user = User.where(:login => user.to_s).first if user.is_a?(Symbol)
request.session[:user] = user.id
end
def current_user
User.find(request.session[:user])
end
end
# spec/spec_helper.rb
RSpec.configure do |config|
config.include SpecTestHelper, :type => :controller
end
Now in any of our controller examples, we can call login(some_user) to simulate logging in as that user.
I should also mention that it looks like you're doing integration testing in this controller test. As a rule, your controller tests should only be simulating requests to individual controller actions, like:
it 'should be successful' do
get :index
response.should be_success
end
This specifically tests a single controller action, which is what you want in a set of controller tests. Then you can use Capybara/Cucumber for end-to-end integration testing of forms, views, and controllers.
Add helper file in spec/support/controller_helpers.rb and copy content below
module ControllerHelpers
def sign_in(user)
if user.nil?
allow(request.env['warden']).to receive(:authenticate!).and_throw(:warden, {:scope => :user})
allow(controller).to receive(:current_user).and_return(nil)
else
allow(request.env['warden']).to receive(:authenticate!).and_return(user)
allow(controller).to receive(:current_user).and_return(user)
end
end
end
Now add following lines in spec/rails_helper.rb or spec/spec_helper.rb
file
require 'support/controller_helpers'
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
config.include ControllerHelpers, :type => :controller
end
Now in your controller spec file.
describe "GET #index" do
before :each do
#user=create(:user)
sign_in #user
end
...
end
Devise Official Link
The easiest way to login with a user on feature tests is to use the Warden's helper #login_as
login_as some_user
As I couldn't make #Brandan's answer work, but based on it and on this post, I've came to this solution:
# spec/support/rails_helper.rb
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } # Add this at top of file
...
include ControllerMacros # Add at bottom of file
And
# spec/support/controller_macros.rb
module ControllerMacros
def login_as_admin
admin = FactoryGirl.create(:user_admin)
login_as(admin)
end
def login_as(user)
request.session[:user_id] = user.id
end
end
Then on your tests you can use:
it "works" do
login_as(FactoryGirl.create(:user))
expect(request.session[:user_id]).not_to be_nil
end
For those who don't use Devise:
spec/rails_helper.rb:
require_relative "support/login_helpers"
RSpec.configure do |config|
config.include LoginHelpers
end
spec/support/login_helpers.rb:
module LoginHelpers
def login_as(user)
post "/session", params: { session: { email: user.email, password: "password" } }
end
end
and in the specs:
login_as(user)
Trying to write some tests for code I've already written, with a view to extending my code using test-driven development.
I have a controller whose index action calls a 'user_info' method, which just collects together some instance variables relying on Sorcery's current_user variable. For example:
def user_info
#current_A = current_user.a
#current_B = current_user.b
end
def index
user_info
// rest of the method goes here
end
I started writing some tests using rspec, just to get a feel for testing this code base. My controller spec is very basic and looks like this:
describe MyController do
describe "GET 'index'" do
get 'index'
response.should be_success
end
end
However, I get the following error when I try to run this spec:
NoMethodError: undefined method 'a' for false:FalseClass
First of all, how do I get my spec to recognize the Sorcery method current_user? And, out of curiosity, why is current_user being flagged as an instance of FalseClass? If it's not calling the Sorcery method, (and I haven't defined current_user anywhere else in my code), should it not appear as nil?
To use Sorcery test helpers you need the following lines in your spec_helper.rb.
The following needs to be in the Rspec.configure block:
RSpec.configure do |config|
config.include Sorcery::TestHelpers::Rails
end
After you have this in place you can use the Sorcery test helpers. For a Controller test you would add the following to your test.
#user = either a fixture or a factory to define the user
login_user
If you don't want to specify #user you can pass an argument.
login_user(fixture or factory definition)
Once you login the current_user should be available to your tests.
logout_user is also available.
See the Sorcery Wiki for information on setting up a user fixture to work with the login_user helper.
Richard, the problem is likely that you don't have a current_user.
To do that, you need to simulate the login process.
You can do that with a controller spec, but I don't have a good example here. I was writing specs on existing code, like you, and it made sense to use request specs instead.
I also don't have one for Sorcery (I should!!) and I am here using Capybara for filling in forms,. Still, here is how my spec looked:
(Here :account is the same as :user would be)
context "when logged in" do
before :each do
#account = Factory.create(:account)
#current_game = Factory(:game_stat)
visit login_path
fill_in 'Username or Email Address', :with => #account.email
fill_in 'Password', :with => #account.password
click_button('Log in')
end
So factories are another matter, mine looked like this:
Factory.define :account do |f|
f.sequence(:username) { |n| "ecj#{n}" }
f.sequence(:email) { |n| "ecj#{n}#edjones.com" }
f.password "secret"
f.password_confirmation {|u| u.password }
end
You don't have to use factories, but you do need to get that session and current_user established.
On important bit is to ensure the user is activated after creation if you're using the :user_activation submodule of Sorcery.
So, if you're using the fabrication gem, that would look like,
Fabricator(:properly_activated_user, :from => :user) do
after_create { |user| user.activate! }
end
As #nmott mentioned you need to do two things:
1) Register text helper methods using:
RSpec.configure do |config|
config.include Sorcery::TestHelpers::Rails
end
2) In your example access current_user through controller.current_user like that:
login_user(user)
expect(controller.current_user).to be_present