Rails newbie. Trying to follow Michael Hartl's tutorial.
Stuck trying to add a helper method to simulate log in an RSpec test:
describe "when the a user has logged in and attempts to visit the page" do
let(:user) { FactoryGirl.create :user }
before do
log_in user
end
it "should redirect the user to next page" do
specify { response.should redirect_to loggedin_path }
end
end
In my spec/support/utilities.rb:
def log_in user
visit root_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Log in"
cookies[:remember_token] = user.remember_token
end
Error:
Failure/Error: log_in user
NoMethodError:
undefined method `cookie_jar' for nil:NilClass
What gives?
Edit, full stack trace:
Index page when the a user has logged in and attempts to visit the page should redirect the user to next page
Failure/Error: log_in user
NoMethodError:
undefined method `cookie_jar' for nil:NilClass
# ./spec/support/utilities.rb:8:in `log_in'
# ./spec/features/pages/index_spec.rb:20:in `block (3 levels) in <top (required)>'
RSpec is very particular about the directory that you place your tests. If you put the test in the wrong directory, it won't automagically mix-in the various test helpers that setup different types of tests. It seems your setup is using spec/features which is not an approved default directory (spec/requests, spec/integration, or spec/api).
Based on the tutorial page, I'm not sure how they have the spec_helper.rb file setup. Though the examples so they are using spec/requests to hold the tests.
You can force RSpec to recognize another directory for request specs by using on of the following:
Manually add the proper module to the test file:
# spec/features/pages/index_spec.rb
require 'spec_helper'
describe "Visiting the index page" do
include RSpec::Rails::RequestExampleGroup
# Rest of your test code
context "when the a user has logged in and attempts to visit the page" do
let(:user) { FactoryGirl.create :user }
before do
log_in user
end
specify { response.should redirect_to loggedin_path }
end
end
Include this in your spec/spec_helper.rb file:
RSpec::configure do |c|
c.include RSpec::Rails::RequestExampleGroup, type: :request, example_group: {
file_path: c.escaped_path(%w[spec (features)])
}
end
Since this is a tutorial I'd recommend following the standard of including require 'spec_helper' at the top of the spec file and that your actual spec/spec_helper.rb file has require 'rspec/rails'
A minor note, you don't need to put a specify inside of an it block. They are aliases of each other, so just use one.
context "when the a user has logged in and attempts to visit the page" do
let(:user) { FactoryGirl.create :user }
before do
log_in user
end
# All of the following are the same
it "redirects the user to next page" do
response.should redirect_to loggedin_path
end
it { response.should redirect_to loggedin_path }
specify "redirects the user to next page" do
response.should redirect_to loggedin_path
end
specify { response.should redirect_to loggedin_path }
end
Note, according to the documentation for capybara, you should be able to put your capybara tests into spec/features. To make this work, ensure you are loading require 'capybara/rspec' in your spec_helper or test spec file directly.
However, looking at the source, I didn't see where they are automatically including that directory. You can also try adding the tag type: :feature to the outer describe block in your test file. Though the more likely solution is to use spec/requests.
Shouldn't you have the "user" argument of the method enclosed in parenthesis?
def log_in(user)
visit root_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Log in"
cookies[:remember_token] = user.remember_token
end
To have a mock cookie jar, you must have either rack-test or rspec-rails gem included in your Gemfile. I think maybe you have included just rspec and maybe missed out rspec-rails.
You also need to ensure you've configured the session store as follows:
config.session_store = :cookie_store
This must have been done in either config/application.rb or some file under config/initializers. If you have configured this in config/environments/development.rb or somewhere else, the Test environment will not be able to pick it up.
Related
Stack: Rails '4.0.4', devise, rSpec, factory_girl, cappybara + selenium-webdriver, mySQL
I'm stll finding myself a little confused controlling user auth in my tests, but this patchwork from other examples is working for now. I have a file called request_helpers.rb in /support that contains:
require 'spec_helper'
include Warden::Test::Helpers
module RequestHelpers
class Login
def self.create_logged_in_user
user = FactoryGirl.create(:user)
login(user)
user
end
def self.login(user)
login_as user, scope: :user, run_callbacks: false
end
end
end
And this is an example of a passing test:
require "spec_helper"
feature "Story Management" do
let( :authorized_user ){ RequestHelpers::Login.create_logged_in_user }
scenario "has a valid factory" do
authorized_user.should be_an_instance_of( User )
end
scenario "Can visit root", js:true do
visit root_path( authorized_user )
page.should have_content( "Your Stories" )
end
end
My question is, How can I logout my authorized user, and log in a new authorized user? Every attempt to utilize devise logout method in my request helper hasn't worked.
Here is my attempt at testing this:
require 'spec_helper'
include Warden::Test::Helpers
Warden.test_mode!
module RequestHelpers
class Login
def self.create_logged_in_user
user = FactoryGirl.create(:user)
login(user)
user
end
def self.login(user)
login_as user, scope: :user, run_callbacks: false
end
def self.logout(user)
logout( user )
end
end
end
scenario "Two users can take turns adding 3 chapters each" do
chapter_string = ValidString.short
player1 = create(:user)
player2 = create(:user)
RequestHelpers::Login.login(player1)
visit new_story_path( player1 )
fill_in "story_title", with: ValidString.short
fill_in "co_author", with: player2.email
click_button "Create Story"
click_link "New Chapter"
fill_in "chapter_body", with: chapter_string
click_button "Create Chapter"
page.should have_content(chapter_string)
RequestHelpers::Login.logout(player1)
RequestHelpers::Login.login(player2)
fill_in "chapter_body", with: chapter_string
click_button "Create Chapter"
page.should have_content(chapter_string)
end
Failed test text:
1) Chapter Management Two users can take turns adding 3 chapters each
Failure/Error: Unable to find matching line from backtrace
SystemStackError:
stack level too deep
# ./spec/support/request_helpers.rb:16
I decided to stop trying to hack around devise here, and just fill in the forms like they suggested in the docs. Logging in and out works fine in this scenario, although slower.
From Devise docs:
These helpers are not going to work for integration tests driven by Capybara or Webrat. They are meant to be used with functional tests only. Instead, fill in the form or explicitly set the user in session.
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...
My application uses Devise for authentication. I want to write integration specs for testing against proper authentication and access prevention.
Somehow, the two don't seem to work together very well. On the devise repo, the README says this on the sign_in and sign_out helpers that Devise gives you for testing:
These helpers are not going to work for integration tests driven by Capybara or Webrat. They are meant to be used with functional tests only. Instead, fill in the form or explicitly set the user in session
So what I'm trying to do to authenticate is filling out the form.
I wrote this (spec/support/signin_helpers.rb):
module SignInHelpers
def sign_in(user)
visit users_login_path
fill_in "Email", with: user.email
fill_in "Passwort", with: "rickroll"
click_button "Einloggen"
end
def login_admin
before(:each) do
sign_in FactoryGirl.create(:admin) # Using factory girl as an example
end
end
def login_user
before(:each) do
user = FactoryGirl.create(:user)
sign_in user
end
end
end
And my tests look like this:
describe "unauthorized access" do
login_user
describe "to Companies#new" do
before { get new_company_path }
specify { response.should redirect_to(root_path) }
end
.
.
.
end
Which seems to work, per se, perfectly fine. No "real" errors thrown. But somehow, somewhere, the authentication gets lost:
5) CompaniesManagement unauthorized access to Companies#index should redirect to "/"
Failure/Error: specify { response.should redirect_to(root_path) }
Expected response to be a redirect to <http://www.example.com/> but was a redirect to <http://www.example.com/users/login>
# ./spec/requests/companies_management_spec.rb:60:in `block (4 levels) in <top (required)>'
What am I doing wrong?
You have put before(:each) in your spec file instead of in support. I mean
describe "unauthorized access" do
before { login_user }
describe "to Companies#new" do
before { get new_company_path }
specify { response.should redirect_to(root_path) }
end
.
.
.
end
In spec/support/signin_helpers.rb you have to write
def login_user
user = FactoryGirl.create(:user)
sign_in user
end
I am trying to give the user of my web app the ability to login with a password. I am rolling my own authentication instead of using a gem. I read this article about refactoring Rspec/Capybara tests:
http://robots.thoughtbot.com/rspec-integration-tests-with-capybara
I liked what I read and decided to give refactoring a try. I created a session helper file for my feature tests.
module Features
module SessionHelpers
def sign_in
user = create(:user)
visit '/authentications/new'
fill_in 'Login', with: user.name
fill_in 'Password', with: user.password
click_button 'Sign in'
end
end
end
I then called the sign_in function in my login tests. Here is a little sample.
require 'spec_helper'
feature "signing in" do
before :each do
User.create(:name => 'user#example.com', :password => 'caplin')
end
scenario "user who logs in with correct credentials" do
sign_in
expect(page).to have_content 'Hi user#example.com'
end
end
Unfortunately, I keep getting this error message:
2) signing in user who logs in with correct credentials
Failure/Error: sign_in
NoMethodError:
undefined method `create' for #<RSpec::Core::ExampleGroup::Nested_3:0x007ffc85012438>
# ./spec/support/features/session_helpers.rb:4:in `sign_in'
# ./spec/features/user_logs_in_spec.rb:13:in `block (2 levels) in <top (required)>'
Basically, I need some way to grab the user I created and pass it into the sign_in function. Any hints?
I'm guessing your first issue is a different test configuration than the one the ThoughBot example has. create is not to my knowledge a default method available in RSpec; I'm going to guess they've added every FactoryGirl method to the testing scope. If you're using FactoryGirl, you can get the same behavior by just namespacing the create command:
def sign_in
user = FactoryGirl.create(:user)
visit '/authentications/new'
fill_in 'Login', with: user.name
fill_in 'Password', with: user.password
click_button 'Sign in'
end
However, this won't quite get you everything that you asked for, since you still won't be able to add a custom user. An easy way for this would allow for a user to be passed in:
def sign_in(user=nil)
user ||= FactoryGirl.create(:user)
...
end
This will create the user for you if you don't pass one in on the sign_in call.
Going back to the spec you posted, you'd want to change it to this:
feature "signing in" do
before :each do
#user = User.create(:name => 'user#example.com', :password => 'caplin')
end
scenario "user who logs in with correct credentials" do
sign_in(#user)
expect(page).to have_content 'Hi user#example.com'
end
end
You'd need to attach the user you created to a variable (#user), then pass it to the sign_in as needed.
Problem in you model
module Features
module SessionHelpers
def sign_in
user = create(:user) # <- this method allow only in FactoryGirl
visit '/authentications/new'
fill_in 'Login', with: user.name
fill_in 'Password', with: user.password
click_button 'Sign in'
end
end
end
i use another way. Create a class and include FactroyGirl methods and Capybara::DSL like this
class Features
include FactoryGirl::Syntax::Methods
include Capybara::DSL
def sign_in
user = create(:user) #<- FactroyGirl
visit '/authentications/new' #<- Capybara
fill_in 'Login', with: user.name #<- Capybara
fill_in 'Password', with: user.password #<- Capybara
click_button 'Sign in' #<- Capybara
self #<- return page
end
end
in spec
feature "signing in" do
let(:login_user) { Features.new }
scenario "user who logs in with correct credentials" do
page = login_user.sign_in
expect(page).to have_content 'Hi user#example.com'
end
end
You can accomplish this by including FactoryGirl in your tests. Your RSpec configuration block (in spec_helper.rb or in the new version of RSpec rails_helper.rb) should look like this:
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
end
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)