I'm having a realy strange situation here.
I created a helper to perform a 'log in' in my integration tests with RSpec/Capybara:
module AuthenticationHelper
def log_in(user = User.new, remember_me = false)
visit new_user_session_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
check("user_remember_me") if remember_me
save_screenshot("/vagrant/screenshot.png")
click_button "Log in"
end
end
As you can see, it has the remember_me argument, with default value false. It works fine in some kind of tests:
context "when 'remember-me' isn't checked" do
before do
log_in create(:user)
reset_session!
visit root_path
end
it "should not stay connected when browser close the session" do
expect(page).to have_selector("a[href='#{destroy_user_session_path}']", count: 0)
end
end
However, when I need to change the remember_me default value calling log_in method, its value simply doesn't change:
context "when 'remember-me' is checked" do
before do
log_in(create(:user), true)
reset_session!
visit root_path
end
it "should stay connected when browser close the session" do
expect(page).to have_selector("a[href='#{destroy_user_session_path}']", count: 1)
end
end
Here is how I configured it in my rails_helper.rb:
.
.
.
require 'helpers/authentication_helper'
RSpec.configure do |config|
config.include AuthenticationHelper, type: :feature
.
.
.
I already tested fixing remember_me with true just to confirm that the method call is the problem.
What I let pass that is causing this behavior?
Testing "remember me" functionality would require the ability to expire a permanent cookie without deleting it (I think that Capybara::Session#reset_session! is deleting all your cookies), and that is a use case that the show_me_the_cookies gem does really well.
Related
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...
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 the following integration tests written for my application's Devise based authentication:
# password_resets_spec.rb
require 'spec_helper'
describe "PasswordResets" do
it "emails user when requesting password reset" do
user = FactoryGirl.create(:user)
reset_email # or else we'll have the confirmation email in the last assertion
visit new_user_session_path
click_link "password"
fill_in "Email", with: user.email
click_button "Send"
current_path.should eq(new_user_session_path)
page.should have_content "Will receive"
last_email.to.should include(user.email)
end
it "doesn't email invalid user when requesting password reset" do
user = FactoryGirl.create(:user)
reset_email # or else we'll have the confirmation email in the last assertion
visit new_user_session_path
click_link "password"
fill_in "Email", with: 'nobody#example.com'
click_button "Send"
current_path.should eq(user_password_path)
page.should have_content "correct"
last_email.should be_nil
end
end
and:
# registers_spec.rb
require 'spec_helper'
describe "Registers" do
it "should inform the user to confirm account" do
user = FactoryGirl.build(:user)
visit new_user_registration_path
fill_in "Username", with: user.username
fill_in "Email", with: user.email
fill_in "Password", with: user.password
fill_in "Confirm password", with: user.password
click_button "Send"
current_path.should eq(root_path)
page.should have_content "You have been sent"
last_email.to.should include(user.email)
end
end
I am using Sidekiq for background jobs and last_email and reset_email come from the following module:
module MailerMacros
def last_email
ActionMailer::Base.deliveries.last
end
def reset_email
ActionMailer::Base.deliveries.clear
end
end
All three of these specs work fine when deactivating devise-async on the User model. When I switch it on, the password reset specs run OK but the register one complains about last_email being nil and I don't understand why. Is the confirmation mail sent out somehow differently compared to the password reset ones?
Note that I have the require 'sidekiq/testing/inline' line in my spec_helper.rb file so that the email sending is done instantaneously and config.action_mailer.delivery_method = :test is set for my test environment so that no actual email sending is taking place.
I have solved the issue with mhfs' help. The problem was that I had config.use_transactional_fixtures set to true in spec_helper.rb and because of this users were created in a transaction and the after_commit hook which would send the email was never called. Password resets apparently didn't run inside transactions, that's why they worked.
So I had to switch use_transactional_fixtures off and use database_cleaner to keep my database tidy.
Here's what I had to modify:
Add gem 'database_cleaner' to my Gemfile.
Obviously modify spec_helper.rb:
config.use_transactional_fixtures = false
Add the following to spec_helper.rb:
config.before(:each) do
with_transaction_callbacks = example.metadata[:with_transaction_callbacks]
if with_transaction_callbacks
DatabaseCleaner.strategy = :truncation
else
DatabaseCleaner.strategy = :transaction
end
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
And lastly redo my block in registers_spec.rb to read:
describe "Registers" do
it "should inform the user to confirm account", with_transaction_callbacks: true do
[ --- 8< snip --- ]
end
end
The magic happens in the second line.
PS. This Stack Overflow topic as well as the article linked from within it also helped.
I have to sign in the user before doing my tests which will use JS. The sign in page does work (except on this test).
require 'spec_helper'
require 'capybara/poltergeist'
include Capybara::DSL
Capybara.javascript_driver = :poltergeist
describe "Application", js: true do
describe "when logged in", js: true do
let(:user) { FactoryGirl.create(:user) }
before do
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
end
it "can authenticate user" do
User.all.first.authenticate(user.password).email.should == user.email
end
end
end
The problem is that clicking the "Sign in" button returns back to the page with "invalid user/password" combination (as if the user is not in the db). The thing is that apparently the use really IS in the database and the password is right (the test "can authenticate user" passes).
I took a screenshot before clicking the "Sign in" button and the fields are populated correctly. I tried to create the user inside the before block but it didn't work either.
The sign in page doesn't have anything special, no javascript or anything. Just a normal form post to the server.
Any ideas?
this was happening to me a while ago while using rspec and poltergeist. The problem was that phantomjs thread wasn't able to see the record created by FactoryGirl in the db. To solve this problem, you have to configure your db connection to be shared in your spec_helper.rb adding the code below.
class ActiveRecord::Base
mattr_accessor :shared_connection
##shared_connection = nil
def self.connection
##shared_connection || retrieve_connection
end
end
# Forces all threads to share the same connection. This works on
# Capybara because it starts the web server in a thread.
ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection
You can find more explanation here in the point number 3.
I'm trying to write integration tests with rspec, factory_girl & capybara. I also have cucumber installed, but I'm not using it (to my knowledge).
I basically want to prepopulate the db with my user, then go to my home page and try to log in. It should redirect to user_path(#user).
However, sessions don't seem to be persisted in my /rspec/requests/ integration tests.
My spec: /rspec/requests/users_spec.rb
require 'spec_helper'
describe "User flow" do
before(:each) do
#user = Factory(:user)
end
it "should login user" do
visit("/index")
fill_in :email, :with => #user.email
fill_in :password, :with => #user.password
click_button "Login"
assert current_path == user_path(#user)
end
end
Returns:
Failures:
1) User flow should login user
Failure/Error: assert current_path == user_path(#user)
<false> is not true.
# (eval):2:in `send'
# (eval):2:in `assert'
# ./spec/requests/users_spec.rb:16
Instead, it redirects to my please_login_path - which should happen if the login fails for any reason (or if session[:user_id] is not set).
If I try to put session.inspect, it fails as a nil object.
If I try to do this in the controller tests (/rspec/controllers/sessions_spec.rb), I can access the session with no problem, and I can call session[:user_id]
If you are using Devise, you'll need to include Warden::Test::Helpers (right after the require of spec_helper is a good place) as outlined in the warden wiki.
The call to session is returning nil because capybara doesn't provide access to it when running as an integration test.
I have the same problems and although filling out a form might be an option for some, I had to roll my own authentication ruby because I was using a third party auth system (Janrain to be exact).... in my tests I ended up using something like this:
Here is what I have in my spec/support/test_helpers_and_stuff.rb
module AuthTestHelper
class SessionBackdoorController < ::ApplicationController
def create
sign_in User.find(params[:user_id])
head :ok
end
end
begin
_routes = Rails.application.routes
_routes.disable_clear_and_finalize = true
_routes.clear!
Rails.application.routes_reloader.paths.each{ |path| load(path) }
_routes.draw do
# here you can add any route you want
match "/test_login_backdoor", to: "session_backdoor#create"
end
ActiveSupport.on_load(:action_controller) { _routes.finalize! }
ensure
_routes.disable_clear_and_finalize = false
end
def request_signin_as(user)
visit "/test_login_backdoor?user_id=#{user.id}"
end
def signin_as(user)
session[:session_user] = user.id
end
end
Then in my request spec, with capybara and selenium, I did the following:
describe "Giveaway Promotion" do
context "Story: A fan participates in a giveaway", js: :selenium do
context "as a signed in user" do
before :each do
#user = Factory(:user)
request_signin_as #user
end
it "should be able to participate as an already signed in user" do
visit giveaway_path
....
end
end
end
end
BTW, I came up with solutions after trying the proposed solutions to this post and this post and neither of them worked for me. (but they certainly inspired my solution)
Good luck!
You've probably moved on from this, but I was just struggling with the same question. Turns out it was a matter of syntax. I was using symbols for :email and :password and I should've been using strings instead ("email" and "password").
In other words, try changing this:
fill_in :email, :with => #user.email
fill_in :password, :with => #user.password
to this:
fill_in "email", :with => #user.email
fill_in "password", :with => #user.password