I am trying to test a feature spec for a user who needs to edit their account model settings. I am new to testing so not sure if my issue is due to how I am setting up my Factory girl associations or if a problem with my database cleaner configurations.
FactoryGirl.define do
factory :user do
first_name "John"
last_name "Smith"
sequence(:email) { |n| "John#{n}#example.com" }
password "pw"
end
end
FactoryGirl.define do
factory :account do
association :owner, factory: :user
name "Account One"
end
end
My spec_helper.rb:
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'rspec/autorun'
require 'shoulda/matchers'
require 'database_cleaner'
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration)
RSpec.configure do |config|
config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.use_transactional_fixtures = false
config.infer_base_class_for_anonymous_controllers = false
config.order = 'random'
config.include FactoryGirl::Syntax::Methods
config.include Devise::TestHelpers, type: :controller
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:twitter] = OmniAuth::AuthHash.new({
:provider => 'twitter',
:uid => '12345'
})
OmniAuth.config.add_mock(:google, {:uid => '12345'})
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
My spec/features/account_spec.rb
require 'spec_helper'
feature 'Account' do
before :each do
#account = create(:account)
#user = #account.owner
sign_in
end
scenario 'a signed in user updates the account settings' do
expect(current_path).to eq edit_account_path(#account.id)
expect(page).to have_content('Edit Account')
fill_in 'Company Name', with: 'New XYZ Co.'
click_button 'Update Account'
expect(page).to have_content('Settings were successfully updated.')
end
scenario 'a signed in user receives an error message when deletes company name' do
fill_in 'Company Name', with: nil
click_button 'Update Account'
expect(page).to have_content("Can't be blank")
end
def sign_in
visit root_path
click_link 'Sign in'
fill_in 'Email', with: #user.email
fill_in 'Password', with: #user.password
click_button 'Sign in'
click_link 'Account'
end
end
If I run just the one spec I get passing tests:
Account
a signed in user updates the account settings
a signed in user receives an error message when deletes company name
Finished in 1.71 seconds
2 examples, 0 failures
But when I run the entire test suite I get errors:
1) Account a signed in user updates the account settings
Failure/Error: expect(current_path).to eq edit_account_path(#account.id)
expected: "/accounts/11/edit"
got: "/accounts/33/edit"
(compared using ==)
# ./spec/features/account_spec.rb:11:in `block (2 levels) in <top (required)>'
Finished in 6.85 seconds
88 examples, 1 failure, 7 pending
Failed examples:
rspec ./spec/features/account_spec.rb:10 # Account a signed in user updates the account settings
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'm using rspec to perform feature tests and I can't save the user in the DB before the log in.
I'm using factory girl to build the object.
fixture are saved in db at the beginning of the test but are not deleted at the end.(maybe because the test fail. I don't know)
So I can not save the user before clicking on logIn and I get this errror message
--
DEPRECATION WARNING: an empty resource was given to Devise::Strategies::DatabaseAuthenticatable#validate. Please ensure the resource is not nil. (called from set_required_vars at app/controllers/application_controller.rb:43)
spec/features/login_to_mainpage_spec.rb (no error are rescued)
require "rails_helper"
feature 'Navigating to homepage' do
let(:user) { create(:user) }
let(:login_page) { MainLoginPage.new }
scenario "login" do
login_page.visit_page.login(user)
sleep(20)
end
end
A simple page object: spec/features/pages_objects/main_login_page.rb
class MainLoginPage
include Capybara::DSL
def login(user)
fill_in 'email', with: user.email
fill_in 'password', with: "password"
click_on 'logIn'
end
def visit_page
visit '/'
self
end
end
my rails_helper
require 'spec_helper'
require 'capybara/rspec'
require 'capybara/poltergeist'
require "selenium-webdriver"
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
Dir[Rails.root.join("spec/features/page_objects/**/*.rb")].each { |f| require f }
RSpec.configure do |config|
config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.use_transactional_fixtures = false
config.before :each do
DatabaseCleaner.start
end
config.after :each do
DatabaseCleaner.clean
end
config.infer_spec_type_from_file_location!
config.include Devise::TestHelpers, :type => :controller
end
Capybara.default_driver = :selenium
Capybara.register_driver :selenium do |app|
Capybara::Selenium::Driver.new(app, :browser => :firefox)
end
in spec helper:
require 'simplecov'
require 'factory_girl'
require 'rspec/autorun'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
ENV["RAILS_ENV"] ||= 'test'
RSpec.configure do |config|
include ActionDispatch::TestProcess
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.disable_monkey_patching!
if config.files_to_run.one?
config.default_formatter = 'doc'
end
config.profile_examples = 10
config.order = :random
Kernel.srand config.seed
end
EDIT 1
I switch "gem 'factory_girl'" to "gem 'factory_girl_rails'"
and add this to application.rb
config.generators do
|g|
g.test_framework :rspec,
:fixtures => true,
:view_specs => false,
:helper_specs => false,
:routing_specs => false,
:controller_specs => true,
:request_specs => true
g.fixture_replacement :factory_girl, :dir => "spec/factories"
end
I still can not save the user in DB.
Everything pass but I put some sleep(10) in by code to refresh my DB and see te records and user is never saved
EDIT 3
My problem is actually very simple. FactoryGirl.create never save the data in DB if I put in my rails_helper:
config.use_transactional_fixtures = true
or
config.before :each do
DatabaseCleaner.start
end
config.after :each do
DatabaseCleaner.clean
end
/spec/factories
FactoryGirl.define do
factory :user do
email 'pierre#tralala.com'
password 'password'
password_confirmation 'password'
end
/spec/support/factory_girl.rb
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
end
spec/features/login_to_mainpage_spec.rb
let(:user) { create(:user) }
scenario "login" do
login_page.visit_page.login(create(:user))
sleep(5)
end
User will not be saved because of the config I cited before.
I need to have data reseted between tests.
EDIT 4
If I'm using the console
RAILS_ENV=test rails c
FactoryGirl.create(:user) it is saved in db.
I don't understand why it does not work in my tests.
Try using let! to create your user.
From the rSpec documentation:
Note that let is lazy-evaluated: it is not evaluated until the first time
the method it defines is invoked. You can use let! to force the method's
invocation before each example.
You said that you are using FactoryGirl but I do not see this in your test. To create my Users with FactoryGirl I always do something like this:
FactoryGirl.create(:user, password: 'test', password_confirmation: 'test', name: 'test')
If setup correctly in your Factory you can just write:
FactoryGirl.create(:user)
To solve your problem with the unique fields FactoryGirl provides you sequences:
sequence :email do |n|
"person#{n}#example.com"
end
factory :user do
email { generate(:email }
end
This will start with "person1#example.com" and add 1 to the number each time FactoryGirl.create(:user) is called.
Here's my request spec:
require 'spec_helper'
describe "Password pages" do
describe "user views his passwords" do
let(:user) { create(:user) }
sign_in(:user)
end
end
spec_helper.rb
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
config.infer_base_class_for_anonymous_controllers = false
config.order = "random"
config.include Capybara::DSL
config.include LoginMacros
end
support/login_macros.rb
module LoginMacros
def sign_in(user)
visit new_user_session_path
fill_in 'Email', with: user.email
fill_in 'Password', with: user.password
click_button 'Sign in'
end
end
When running spec I get error:
/spec/requests/account_pages_spec.rb:9:in `block (2 levels) in <top (required)>': undefined method `sign_in' for #<Class:0x007fd4f97183f8> (NoMethodError)
I use this cool technics but another way:
Create page class:
class PageObject
include Capybara::DSL
def visit_page(page)
visit(page)
self
end
def login(user)
fill_in 'Email', with: user.email
fill_in 'Password', with: user.password
click_button 'Sign in'
end
end
create feature instead describe:
require 'spec_helper'
feature 'test authozire page' do
let(:login_page) { PageObject.new }
let(:user) { create(:user) }
scenario 'login page have field' do
page = login_page.visit_page(new_user_session_path)
expect(page.have_field?('email')).to be_true
expect(page.have_field?('password')).to be_true
end
scenario "user can login" do
login_page.visit_page(new_user_session_path).login(user)
# more awesome test
end
end
Testing devise sign in using capybara. It seems something is wrong as i cannot test sign in using rspec and capybara. Im using factory girl to define user
FactoryGirl.define do
factory :user do
email 'admin#revol-tech.com.np'
password 'bhaktapur'
password_confirmation 'bhaktapur'
admin true
name 'admin'
confirmation_sent_at "#{DateTime.now}"
confirmation_token 'anupsumhikichiki'
confirmed_at "#{DateTime.now}"
username 'username'
end
end
Here is my spec_helper.rb
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'rspec/autorun'
require 'capybara/rspec'
require 'database_cleaner'
# FactoryGirl.find_definitions
Capybara.current_driver = :selenium
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
# config.after(:each) { }
config.after(:each) do
DatabaseCleaner.clean
Warden.test_reset!
end
config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.use_transactional_fixtures = false
# If true, the base class of anonymous controllers will be inferred
# automatically. This will be the default behavior in future versions of
# rspec-rails.
config.infer_base_class_for_anonymous_controllers = false
end
And here is my spec
require_relative '../spec_helper.rb'
include Warden::Test::Helpers
Warden.test_mode!
feature "the signin process" do
before :each do
#user_attr = FactoryGirl.attributes_for(:user)
# #user = FactoryGirl (:user)
User.create!(#user_attr)
end
scenario "signs me in" do
# login_as #user, :scope => :user
visit '/'
fill_in 'Login', :with => "admin#revol-tech.com.np"
fill_in 'Password', :with => "bhaktapur"
click_button 'Sign in'
page.should have_content "Signed in successfully"
Warden.test_reset!
end
end
Also my User model is set to confirmable
Instead of adding all the confirmable data points.. should use
#user = FactoryGirl.build(:user)
#user.skip_confirmation!
#user.save!
Then within your scenario
fill_in 'Login', :with => #user.email
fill_in 'Password', :with => #user.password
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