I have a feature spec with Capybara for a login page, and I am using FactoryGirl + DatabaseCleaner
require 'rails_helper'
feature 'Admin signs in' do
background do
FactoryGirl.create(:user)
end
scenario 'with valid credentials' do
visit admin_root_path
fill_in 'user_email', :with => 'email#email.com'
fill_in 'user_password', :with => 'testpassword'
click_button 'Sign in'
expect(page).to have_content('Dashboard')
end
scenario 'with invalid credentials' do
visit admin_root_path
fill_in 'user_email', :with => 'email#email.com'
fill_in 'user_password', :with => 'wrongpassword'
click_button 'Sign in'
expect(page).to have_content('Admin Login')
end
end
running the test, I get the following error:
1) Admin signs in test with invalid credentials
Failure/Error: FactoryGirl.create(:user)
ActiveRecord::RecordInvalid:
Validation failed: Email has already been taken
I thought DatabaseCleaner would revert the changes, but it looks like the user record persist in the database till the second scenario block.
How can I make sure that the database is cleaned after the first scenario?
I configured Database cleaner following this post
# support/database_cleaner_spec.rb
RSpec.configure do |config|
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
config.before(:each, :js => true) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end
I have also updated the spec helper file with:
config.use_transactional_fixtures = false
I was wrongly assuming that configuration files in spec/support folder were automatically loaded, but it turns out that I had to uncomment the following line in spec/rails_helper.rb
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
The DatabaseCleaner config file was correct, it just wasn't loaded at all.
Make sure you have the following configuration in spec/rails_helper.rb
RSpec.configure do |config|
config.use_transactional_fixtures = true
end
The idea is to start each example with a clean database, create whatever data is necessary for that example, and then remove that data by simply rolling back the transaction at the end of the example.
use these settings:
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation)
end
If by any chance you are using MySQL and altering tables (reset auto increments or whatever DDL operation) within a method within a test, the transaction strategy will fail and your database will not be cleaned.
In order to fix, you have to declare a config block like this:
config.before(:each, :altering_database => true) do
DatabaseCleaner.strategy = :truncation
end
And add this config to your test context:
context "when you alter the DB", :altering_database => true do...
Note: this will slow down your tests, so be careful not to abuse it.
config.before(:example) do
DatabaseCleaner.clean_with(:truncation)
end
worked for me!
Related
I was writing test for registeration form and i got error "Email has already been taken"
I have googled this problem and come up this gem
gem 'database_cleaner', git: 'https://github.com/DatabaseCleaner/database_cleaner.git'
But it still didn't fixed the bug
I may messed up with the database_cleaner setup
spec_helper.rb
require 'database_cleaner'
Dir["./spec/support/**/*.rb"].sort.each { |f| require f}
RSpec.configure do |config|
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
expectations.syntax = :should
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.before(:suite) do
DatabaseCleaner[:active_record].strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end
rails_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'spec_helper'
require 'rspec/rails'
require 'capybara/rspec'
ActiveRecord::Migration.maintain_test_schema!
RSpec.configure do |config|
config.use_transactional_fixtures = true
config.infer_spec_type_from_file_location!
config.filter_rails_from_backtrace!
end
factories.rb
FactoryGirl.define do
factory :identity do |f|
f.name Faker::Name.name
f.email Faker::Internet.email
f.password Faker::Internet.password(4,40)
end
end
identity_spec.rb
it "Registration successfully" do
user = FactoryGirl.create(:identity)
visit(new_identity_path)
fill_in('Name', :with => user.name)
fill_in('Email', :with => user.email)
fill_in('Password', :with => user.password)
fill_in('Password confirmation', :with => user.password)
click_button 'Register'
page.should have_content("You'r successfully logged in")
end
UPDATE:
it "Invalid password" do
user = FactoryGirl.create(:identity)
puts "USER Email: #{user.email}"
visit('/login')
fill_in('Email', :with => user.email)
fill_in('Password', :with => "incorrect")
click_button 'Login'
page.should have_content("Invalid info")
end
it "Registration successfully" do
puts "IDENTITY COUNT: #{Identity.count}"
user = FactoryGirl.create(:identity)
puts "USER Email: #{user.email}"
# visit(new_identity_path)
# fill_in('Name', :with => user.name)
# fill_in('Email', :with => user.email)
# fill_in('Password', :with => user.password)
# fill_in('Password confirmation', :with => user.password)
# click_button 'Register'
# page.should have_content("You'r successfully logged in")
end
Output
USER Email: litzy.legros#rogahnskiles.net
.IDENTITY COUNT: 0
USER Email: litzy.legros#rogahnskiles.net
.
.
Looks like you might have it slightly misconfigured.
Try putting require 'database_cleaner' in your spec_helper.rb file.
And then include require 'spec_helper' in your rails_helper.rb file.
If that doesn't fix it, then please include the rest of your spec_helper.rb and rails_helper.rb files in your question.
UPDATE
As far as I can tell, everything looks good in your helper files. The only difference I see between your implementation and my own is that you've got the strategy defined with:
DatabaseCleaner[:active_record].strategy = :transaction
whereas mine is simply:
DatabaseCleaner.strategy = :transaction
I don't think that would be the issue but it's worth a shot.
If that doesn't fix it, can you throw a couple of puts statements in your spec tests and let us know the output? Like so:
puts "IDENTITY COUNT: #{Identity.count}"
user = FactoryGirl.create(:identity)
puts "USER EMAIL: #{user.email}"
This will let us know two things:
is database_cleaner actually not working (count should be 0 if it is working)
is Faker using the same exact email address every time it's used.
Capybara tests should not use transactions. Turn off transactions in rails_helper.rb with:
config.use_transactional_fixtures = false
The database_cleaner docs explain why:
You'll typically discover a feature spec is incorrectly using transaction instead of truncation strategy when the data created in the spec is not visible in the app-under-test.
A frequently occurring example of this is when, after creating a user in a spec, the spec mysteriously fails to login with the user. This happens because the user is created inside of an uncommitted transaction on one database connection, while the login attempt is made using a separate database connection. This separate database connection cannot access the uncommitted user data created over the first database connection due to transaction isolation.
I want to be able to always have access to my seed data on my test database.
I understand database_cleaner will remove everything if it's set up that way.
I try to remove everything and then reloading the seed, but when I try to use js: true on a test, the seed never gets loaded so i get errors saying data does not exist.
My spec_helper.rb
RSpec.configure do |config|
# before the entire test suite runs, clear the test database out completely
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation)
end
# sets the default database cleaning strategy to be transactions (very fast)
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
# For these types of tests, transactions won’t work. We must use truncation
config.before(:each, js: true) do
DatabaseCleaner.strategy = :truncation
end
# hook up database_cleaner around the beginning and end of each test, telling it to execute whatever cleanup strategy we selected beforehand.
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
# reload the seed so we have data to play with
end
config.before :all do
Rails.application.load_seed
end
end
When in my view_spec I have something like this
require 'spec_helper'
describe 'my/path', type: :view do
before do
#user = create(:user)
#user.permissions << Permission.first
login_as(#user)
visit my_path
end
it 'should have a valid user, just for kicks' do
#user.should be_valid
end
it 'should be in the path i said' do
expect(current_path).to eq(my_path)
end
describe 'click submit button', js: true do
it 'should take me to a different path' do
click_link('button_1')
expect(current_path).to eq(my_new_path)
end
end
end
The first two test will run and be ok with creating that user, but as soon as it hits that last test with js: true, it no longer has Permission in the database.
Is there a way to tell database_cleaner to only delete the data added by rspec? and not the seed?
Or maybe even tell it to not delete certain tables?
Any help would be appreciated.
Try to use :truncation for all tests with:
DatabaseCleaner.strategy = :truncation
RSpec.configure do |config|
config.before(:each) do
DatabaseCleaner.clean
Rails.application.load_seed
end
end
There also may be an issue with your seeds and not with DatabaseCleaner. You should debug your database state right in the failing test using puts statements or debugger (e.g. pry-byebug).
So I've setup my RSpec environment to use a truncation cleaning strategy for my RSpec Capybara tests but I still find that something is still wrapping my test in a transaction when I use Webkit as my Javascript driver.
I don't have this problem with Selenium, which has got me stumped.
Here's the relevant RSpec config with webkit:
Capybara.javascript_driver = :webkit
Capybara.register_driver :webkit do |app|
Capybara::Webkit::Driver.new(app).tap do |driver|
driver.allow_url "fonts.googleapis.com"
driver.allow_url "dl.dropboxusercontent.com"
end
end
config.before(:suite) do
DatabaseCleaner.clean_with :truncation
DatabaseCleaner.clean_with :transaction
end
config.after(:each) do
ActionMailer::Base.deliveries.clear
end
config.around(:each, type: :feature, js: true) do |ex|
DatabaseCleaner.strategy = :truncation
DatabaseCleaner.start
self.use_transactional_fixtures = false
ex.run
self.use_transactional_fixtures = true
DatabaseCleaner.clean
end
And my feature test looks like this:
feature "profile", js: true do
describe "a confirmed user with a valid profile" do
before(:each) do
#user = FactoryGirl.create :user
signin(#user.email, #user.password)
end
scenario 'can edit name' do
visit edit_user_profile_path
fill_in :user_name, with: 'New name'
click_button :Submit
#user.reload
expect(#user.name).to eq('New name')
expect(current_path).to eq show_user_path
end
end
end
If I run this test with Webkit it fails, but with Selenium it passes.
I've experimented with some debugging. If I put a debugger statement in the #update action I see that it updates the database correctly. If I connect to the test database at that time I can see the new information in the database, which means that this update cannot be wrapped in a transaction. However, but in the debugger in the .spec #user still see the original name as generated by FFaker in factory_girl. This leads me to believe that the test is ran inside a transaction.
When I change my JavaScript driver to Selenium it all works fine.
Any ideas?
Wow. I found the problem almost immediately after posting the question. No transactions were involved.
It was a race issue between the backend and webkit/selenium front end. With Webkit the test was executing the #user.reload and the expect statements before the controller had a chance to update the database. With Selenium it was the other way around.
The trick is to make Capybara wait for the page reload. I changed my test to this:
scenario 'can edit name' do
visit edit_user_profile_path
fill_in :user_name, with: 'New name'
click_button :Submit
expect(current_path).to eq show_user_path
#user.reload
expect(#user.name).to eq('New name')
end
I have tried getting solutions from the SO questions listed at the bottom but my problem is that I am using Capybara and FactoryGirl and I cannot seem to load seeds.rb from anywhere without causing many tests that are completely separate from the seed data from breaking.
Most of the error messages are variations on page.should_not have_content user.email
after a test where I try delete a user that I made through a factory. These are tests that passed fine until I loaded the seed data.
How to load db:seed data into test database automatically?
Prevent Rails test from deleting seed data
What is the best way to seed a database in Rails?
How to auto-load data in the test database before to test my application?
What I have is a single admin group, assigned the admin permission and an admin user in the seeds.rb linked together
One possibility is calling a factory in my seeds.rb to populate this data but I have not yet figured out how.
seeds.rb
User.find_or_create_by_email(email: "admin#admin.admin",
password: "admin", password_confirmation: "admin")
%w{admin supermod}.each {|w| Group.find_or_create_by_name(w)}
%w{admin mod player}.each {|w| Permission.find_or_create_by_name(w)}
g = Group.find_by_name("admin")
g.permission_id = Permission.find_by_name("admin").id
puts "failed to add admin permission to admin group" unless g.save
u = User.find_by_email("neonmd#hotmail.co.uk")
ug = UserGroup.new
ug.group_id = Group.find_by_name("admin").id
ug.user_id = u.id
puts "failed to add admin group to #{u.name}" unless u.save && ug.save
Failing test
This passes before I load seeds.rb
it "lets you remove user from group" do
user = Factory.create(:user)
admin = admin_login
group = add_group
add_user_to_group user, group
click_link "delete_#{user.email}"
page.should_not have_content user.email
end
def admin_login
admin = Factory.build(:admin)
visit login_path
fill_in "email", :with => admin.email
fill_in "password", :with => admin.password
click_button "Log In"
return admin
end
def add_group
group = Factory.build(:group)
visit new_group_path
fill_in "group_name", :with => group.name
click_button "Submit"
return group
end
def add_user_to_group (user, group)
visit groups_path
click_link "#{group.name}_users"
fill_in "user_email", :with => user.email
click_on "Add User"
end
I do not know the reason but requiring the seeds.rb file instead of loading it in the test environment works and you can just call seed_data on the tests that need it.
spec/helpers.rb
def seed_data
require "#{Rails.root}/db/seeds.rb"
end
better solution
spec/spec_helper.rb
RSpec.configure do |config|
config.before(:suite) do
require "#{Rails.root}/db/seeds.rb"
end
........
end
In Rails 4.2.0 and RSpec 3.x, this is how my rails_helper.rb looks.
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.fixture_path = "#{::Rails.root}/spec/fixtures"
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
config.use_transactional_fixtures = false
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
config.before(:each, :js => true) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
config.before(:all) do
Rails.application.load_seed # loading seeds
end
end
I'm clicking show and destroy links with Capybara that should be in the first table row in an employees table. The table should sort so that the most recently modified employee is on the top, based on the updated_at timestamp.
Before the example a valid employee must be created that passes authentication. Then at the beginning of the example an employee must be created to test with. This is the employee that should always be at the top of the table because it's supposed to have been modified most recently. Sometimes it is and sometimes it isn't. Adding a sleep call fixes this, and I'm wondering if that's valid or if I've got it wrong. I thought adding a sleep call in a test was a Bad Thing. I also thought if the authentication employee is created before the example then even if my spec is running really fast then it should still have an earlier updated_at timestamp.
The authentication macro:
module AuthenticationMacros
def login_confirmed_employee
# create a valid employee for access
Factory :devise_confirmed_employee,
:username => 'confirmed.employee',
:password => 'password',
:password_confirmation =>'password'
# sign in with valid credentials
visit '/employees/sign_in'
fill_in 'Username', :with => 'confirmed.employee'
fill_in 'Password', :with => 'password'
click_on 'Sign In'
end
end
The request spec in question:
require 'spec_helper'
include AuthenticationMacros
describe "Employees" do
before do
login_confirmed_employee
end
# shows employees
it "shows employees" do
sleep 1.seconds
Factory :employee_with_all_attributes,
:username => 'valid.employee',
:email => 'valid.employee#example.com',
visit '/employees'
within 'tbody tr:first-child td:last-child' do; click_on 'Show', end
page.should have_content 'valid.employee'
page.should have_content 'valid.employee#example.com'
end
# destroys employees
it "destroys employees" do
sleep 1.seconds
Factory(:employee)
visit '/employees'
within 'tbody tr:first-child td:last-child' do; click_on 'Delete', end
page.should have_content 'Employee was successfully deleted.'
end
end
And for good measure here's my spec_helper:
require 'spork'
Spork.prefork do
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
ActiveSupport::Dependencies.clear
require 'email_spec'
RSpec.configure do |config|
config.include EmailSpec::Helpers
config.include EmailSpec::Matchers
end
end
Spork.each_run do
require 'rspec/rails'
require 'factory_girl_rails'
require 'capybara/rspec'
require 'capybara/rails'
require 'shoulda'
RSpec.configure do |config|
config.mock_with :rspec
config.use_transactional_fixtures = false
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end
end
Sometimes I end up on the show page or destroying the authentication-required employee instead of the example employee.It never happens though if I add the 1 second sleep call.
Nope, it fact it's a very bad solution because it increases tests execution time.
You could mock Time.now method call using one of the following solutions:
https://github.com/bebanjo/delorean
https://github.com/jtrupiano/timecop