I am experiencing strange very test behavior, with logged in state being handled inconsistently.
The spec logs a user in, visits a (nested or un-nested) index page, and checks that the correct content is displayed. Records are fetched asynchronously, though I don't think this should have an impact.
When each spec is run individually, they each pass. When all specs are run together, they fail because the expected content is missing. Using save_and_open_page reveals this is because the login page is being rendered, rather than the expected index page.
Why does rspec think the user is not signed in when all specs are run together, yet each spec passes individually?
The tests look something like this
let(:user) {create :user}
let(:team) {create :team}
let(:country) {create :country}
before :each do
login_as( user, scope: :user )
end
describe 'unnested' do
it 'should have the expected content', :js do
visit users_path
is_expected.to have_content "some content on the page"
end
end
describe 'nested by team' do
it 'should have the expected content', :js do
visit team_users_path(team)
is_expected.to have_content "some content on the page"
end
end
describe 'nested by nationality' do
it 'should have the expected content', :js do
visit country_users_path(country)
is_expected.to have_content "some content on the page"
end
end
The specs all require javascript (I don't know whether that is important here).
Authentication is handled by Devise, and my rails_helper.rb includes
config.append_after(:each) do
DatabaseCleaner.clean
Warden.test_reset!
end
Why does rspec think the user is not signed in when all specs are run together, yet each spec passes individually?
It took a long time to get to the bottom of this. Posting this hear in case it is of help to anyone else encountering the same issue.
After much searching I eventually found this small mention that login_as may not work with Poltergeist when js is enabled on your test scenarios.
I tried the suggested fix to deal with shared DB connections. Unfortunately this resulted in the following errors:
PG::DuplicatePstatement at /session/users/signin
ERROR: prepared statement "a1" already exists
I tried using the Transactional Capybara gem, but this did not seem to work well with Poltergeist.
Eventually I abandonned login_as completely, and instead wrote a short method that visits the login page, fills in email and password, and logs in that way.
This solution appears to be working. It adds a little overhead, so I'm only using it for tests with JS.
If you are using Capybara gem then there is no need to use :js with test cases
What I did if this helps-
scenario "visit with user signed in" do
user = FactoryGirl.create(:user)
login_as(user, :scope => :user)
visit "/"
expect(current_path).to eq('/')
expect(page).to have_title "Some Random Title"
end
The other way you can login user using feature specs like-
feature 'User signs in' do
before :each do
#user = FactoryGirl.create(:user)
end
scenario "Signing in with correct credentials" do
visit "/"
fill_in "Email", with: #user.email
fill_in "Password", with: #user.password
click_button "Log In"
expect(current_path).to eq("/login/useremail/verification")
expect(page).to have_content "Signed in successfully"
end
end
If your pages are ajax then refer to this https://robots.thoughtbot.com/automatically-wait-for-ajax-with-capybara
Related
I am trying to test what happens after logging in through Devise gem. For example, I have the controller to go to student_dashboard_path after users successfully login.
How can I test this with Capybara and Rspec?
I currently have this in:
/spec/features/user_signs_in_sees_dashboard_spec.rb
require 'rails_helper'
feature 'User sign in' do
scenario 'successfully from sign in page and sees student dashboard' do
sign_in
visit student_dashboard_path
expect(current_path).to eq(student_dashboard_path)
end
end
and I have this in:
/spec/support/features/sign_in.rb
module Features
def sign_in
visit user_session_path
fill_in 'Email', with: User.first.email
fill_in 'Password', with: User.first.password
click_button 'Log in'
end
end
and I am getting this error message:
1) User signs in successfully from sign in and sees dashboard
Failure/Error: expect(current_path).to eq(student_dashboard_path)
expected: "/student/dashboard"
got: "/student/login"
I am not sure why I am not able to log in and see the student dashboard.
I'm leaving my original answer below for anyone still on Capybara < 2.5.0 but in 2.5.0 you can now do
expect(page).to have_current_path(<expected path>)
and it will use Capybara's waiting behavior while checking for the path
---- Below is only for Capybara version < 2.5.0
expect(current_path).to eq(...) doesn't wait for the path to change, it just compares to the current path at the time it's called. If you put a sleep after the click button I bet it works. A better solution would be to have something like
expect(page).to have_text('You are now logged in')
after the click_button. That would cause capybara to wait until the log in has completed, the page loads (and the logged in notice appears), and therefore until the current_path has changed too.
Use capybara save_and_open_page in middle to figure out if the fields are properly set. If you work on the same machine you can switch to selenium to test out on the real browser.
Also be sure that this code does not need any JS to work because default capybara matchers will not be able to run it.
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 using rspec, capybara and Selenium to test my whole application stack. I've turned off transactional fixtures, and I'm using database cleaner to clean my database only after the whole suite has been run. These allows me to test things based using objects created in preceding tests.
Anyway, let's say I want to create user a999 (via a form, so a test in itself) and then proceed to test logging him out and logging him back in.
def sign_up(first_name, last_name, profile_name, email, password)
visit "/"
click_link "Register"
fill_in('First name', with: first_name)
fill_in('Last name', with: last_name)
fill_in('Profile name', with: profile_name)
fill_in('Email', with: email)
fill_in('Password', with: password)
fill_in('Password confirmation', with: password)
click_button 'Sign up'
end
feature "user a999 sign up", js: true do
before(:each){
sign_up( #a999.first_name, #a999.last_name, #a999.profile_name, #a999.email, #a999.password )
}
scenario "welcome message" do
expect(page).to have_content ("Welcome," + #a999.first_name)
end
scenario "can log out" do
end
scenario "can log in" do
end
end
The code above almost works. This is what happens when it's run:
The before block signs up the user before the "welcome message" expectation (I see it physically happening in Firefox thanks to Selenium), and then the welcome message appears after a redirect so the "welcome message" spec passes.
However, because I have the before block set to 'each' the before block is run another two times, meaning I now have three a999 users in the database.
Of course, and setting the before block to (:all) should fix this problem. The user is signed up one, and we go from there, signing the exact same user in and out. It's a feature test that tests the whole stack remember, so I want to do this properly, emulate how a real user will be using my app.
def sign_up(first_name, last_name, profile_name, email, password)
visit "/"
click_link "Register"
fill_in('First name', with: first_name)
fill_in('Last name', with: last_name)
fill_in('Profile name', with: profile_name)
fill_in('Email', with: email)
fill_in('Password', with: password)
fill_in('Password confirmation', with: password)
click_button 'Sign up'
end
feature "user a999 sign up", js: true do
before(:all){
sign_up( #a999.first_name, #a999.last_name, #a999.profile_name, #a999.email, #a999.password )
}
scenario "welcome message" do
expect(page).to have_content ("Welcome," + #a999.first_name)
end
scenario "can log out" do
end
scenario "can log in" do
end
end
But with this code nothing happens at all. Seriously, just nothing. Selenium doesn't follow the code in the before block at all! Firefox doesn't even start up.
Why is this? I mean, that should work at the very least.
before(:each) = signs user up before my eyes
before(:all) = completely dead
I can't explain why nothing comes up at all, but based on numerous posts*, you can't reasonable use before(:all) with capybara, since it resets the session between each example.
*Related posts:
Capybara and before(:all) in rspec
capybara/selenium with rspec before :all hook
I am working on tests but running in to a road block on pages that require a current_user. I am using minitest, capybara, factorygirl, and authlogic, in rails 3.2.9 with ruby 1.9.3p327. I installed minitest as a separate gem, and seem to have the test environment working correctly.
I have a factory that creates a valid user...I call that factory from in a test like this:
describe "UsersAcceptanceTest" do
it "must load and include content" do
FactoryGirl.create(:user)
visit users_path
page.must_have_content("cPanel")
end
end
The FAIL is correct in informing me that the content "cPanel" was not found (cPanel is a link available to logged in users). The fail error goes on to alert me that it was not found in "log in, forgot password, contact" ... which of course means that the test routed correctly to users_path, but was redirected by authlogic because the user is not logged in. Users cant create themselves in my system and therefor are not auto-logged in on create.
How to I also get the factory to create a new user session with the newly created user?
You can do it this way:
visit signin_path
fill_in 'email', with: user.email
fill_in 'password', with: user.password
click_button "Log in"
Just edit it according to your login page structure.
I don't know about minitest, but in rspec I'd create the separate method with this codedef sign_in...end and put it to support\utilities.rb.
Then your code would be looking like that:
describe "UsersAcceptanceTest" do
let(:user) { Factory(:user) }
subject { page }
it "must load and include content" do
sign_in user
visit users_path
it { should have_link("cPanel", href: cpanel_path) }
end
end
As you can see I've edited your code a little bit more.
I have a project in which I am using RSpec and Capybara to do unit testing. I have fully flushed out model and controller tests which are passing nicely and handle the heavy lifting for pre-database validation.
I am now testing user experience and front end items and want to know how i could verify that a form did NOT submit. If a user mismatches passwords or some other erred data, I have script to set the error and prevent submission.
I know i could search for error text but where there a way to check that the 'submit' never happened and feel confident that no server trip was made.
I want something like:
it "should not sumbit if user name is less than 3 characters" do
visit /edit_account_settings(#user)
fill_in "username", :with => "fo"
click_button "SAVE"
# HOW DO I DO THIS?
expect( ... ).not_to submit_to_server
end
This is not something that you should be testing in an integration test. In integration tests you are taking the point of view of the end user, and therefore you should only be testing what the user can actually see. If the only evidence the user sees that the form has not been submitted is an error message, then that is what you should test for.
In integration tests we most test what the user would see like if an empty field is given then there must be error message according to validations. But still if you want you check as follow
describe "User Registration" do
before do
visit /edit_account_settings(#user)
fill_in "username", :with => "fo"
click_button "SAVE"
end
it "should not sumbit if user name is less than 3 characters" do
page.should have_content "your error message"
end
it "should create the user and redirect to blah_path" do
current_path.should eq blah_path
end
it "should add user in users table" do
expect { user.create }.to change(User, :count).from(0).to(1)
end
end