I have a feature/integration spec that runs through an entire user flow. By the end of that user flow, a page in the app will display a few records from my Postgres database. When I run the test by itself, it passes. I can see it saving correctly through the various steps since the selenium driver opens up firefox: Capybara.current_driver = :selenium. However, this spec fails regularly, almost predictably, when it is run after a bunch of controller specs. In those controller specs the only interesting thing that I am doing is running this login function:
module DeviseMacros
def login_user
before(:each) do
#request.env['devise.mapping'] = Devise.mappings[:user]
user = create(:user_with_active_account)
sign_in user
end
end
end
So I call it like this:
describe AwesomeController do
login_user
it 'responds with 200' do
get :new
expect(response.status).to eq 200
end
end
When run after a controller spec I can immediately see that the test will fail since certain UI elements should appear depending on what's in the DB and clearly they are not present.
My DatabaseCleaner strategy is as follows:
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
Through trial and error I changed
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
to
config.before(:each) do
DatabaseCleaner.strategy = :truncation
end
And walla, it passes. Of course now the test suite takes over 2x as long.
I have tagged all my :selenium tests with js: true in order to ensure :truncation is used for them but that really doesn't matter since :selenium is already driving those. However, most importantly, this feature spec still fails after those controller specs.
Where else should I be looking? How do I proceed with debugging?
The only other unique thing I have in here that may be related is:
# In spec/support/shared_db_connection.rb
# https://gist.github.com/josevalim/470808
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
Any advice or ideas on how to proceed with debugging would be greatly appreciated.
If you have any additional questions please ask. Thank you
Update: June 1, 2016
The exact line of code that causes the failure is:
module DeviseMacros
def login_user
before(:each) do
#request.env['devise.mapping'] = Devise.mappings[:user]
user = create(:user_with_active_account)
sign_in user # <----- THIS GUY RIGHT HERE! When removed, the ControllerSpec fails but the integration test passes.
end
end
end
So for some reason it appears that hitting the DB with this controller spec (which uses :transaction strategy) effects the feature spec (which uses :truncation strategy).
I'm debating just not hitting the DB at all in the controller specs when trying to authenticate a devise user, which is cool with me, but I feel like it shouldn't have to be that way. Any ideas on how to proceed if I do indeed want to be able to use the sign_in method?
Thank you
Remove the shared connection hack, since it causes more issues than it's worth, and update your Database Cleaner config to the recommended one from the database cleaner README - https://github.com/DatabaseCleaner/database_cleaner#rspec-with-capybara-example
Related
I'm building a toy chat application using Rails 4.2.7, and am writing specs for my controllers using rspec 3.5. My Api::ChatroomsController requires a user to be logged in in order to create a chatroom, so I have created a Api::SessionsHelper module to create sessions from within the Api::ChatroomsController spec.
# app/helpers/api/sessions_helper.rb
module Api::SessionsHelper
def current_user
User.find_by_session_token(session[:session_token])
end
def create_session(user)
session[:session_token] = user.reset_session_token!
end
def destroy_session(user)
current_user.try(:reset_session_token!)
session[:session_token] = nil
end
end
# spec/controllers/api/chatrooms_controller_spec.rb
require 'rails_helper'
include Api::SessionsHelper
RSpec.describe Api::ChatroomsController, type: :controller do
before(:all) do
DatabaseCleaner.clean
User.create!({username: "test_user", password: "asdfasdf"})
end
user = User.find_by_username("test_user")
context "with valid params" do
done = false
# doesn't work if using a before(:all) hook
before(:each) do
until done do
create_session(user)
post :create, chatroom: { name: "chatroom 1" }
done = true
end
end
let(:chatroom) { Chatroom.find_by({name: "chatroom 1"}) }
let(:chatroom_member) { ChatroomMember.find_by({user_id: user.id, chatroom_id: chatroom.id}) }
it "responds with a successful status code" do
expect(response).to have_http_status(200)
end
it "creates a chatroom in the database" do
expect(chatroom).not_to eq(nil)
end
it "adds the chatroom creator to the ChatroomMember table" do
expect(chatroom_member).not_to eq(nil)
end
end
end
I'm using a before(:each) hook with a boolean variable done to achieve the behavior of a before(:all) hook for creating a single session.
If I use a before(:all) hook, I get the error:
NoMethodError: undefined method `session' for nil:NilClass`
I put a debugger in the create_session method of the Api::SessionsHelper module to check self.class and in both cases, when I use before(:each) and when I use before(:all), the class is:
RSpec::ExampleGroups::ApiChatroomsController::WithValidParams
However when using the before(:each) hook, session is {}, while in the before(:all) hook, session gives the NoMethodError above.
Anybody know what causes this error?
You need to include the helper in the test block:
RSpec.describe Api::ChatroomsController, type: :controller do
include Api::SessionsHelper
end
You can also avoid duplication by including common spec helpers in spec/rails_helper.rb
RSpec.configure do |config|
# ...
config.include Api::SessionsHelper, type: :controller
end
This is also where you should put the database_cleaner config. You should use to clean between every spec not just before all as that will lead to test ordering issues and flapping tests.
require 'capybara/rspec'
#...
RSpec.configure do |config|
config.include Api::SessionsHelper, type: :controller
config.use_transactional_fixtures = false
config.before(:suite) do
if config.use_transactional_fixtures?
raise(<<-MSG)
Delete line `config.use_transactional_fixtures = true` from rails_helper.rb
(or set it to false) to prevent uncommitted transactions being used in
JavaScript-dependent specs.
During testing, the app-under-test that the browser driver connects to
uses a different database connection to the database connection used by
the spec. The app's database connection would not be able to access
uncommitted transaction data setup over the spec's database connection.
MSG
end
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
config.before(:each, type: :feature) do
# :rack_test driver's Rack app under test shares database connection
# with the specs, so continue to use transaction strategy for speed.
driver_shares_db_connection_with_specs = Capybara.current_driver == :rack_test
if !driver_shares_db_connection_with_specs
# Driver is probably for an external browser with an app
# under test that does *not* share a database connection with the
# specs, so use truncation strategy.
DatabaseCleaner.strategy = :truncation
end
end
config.before(:each) do
DatabaseCleaner.start
end
config.append_after(:each) do
DatabaseCleaner.clean
end
end
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).
I have a JS feature spec I'm trying to run with Capybara Webkit. It doesn't seem to be able to find my database records however.
The spec in question looks like this
it "should allow pledging to a Hardback level", js: true do
book = FactoryGirl.create :book
visit book_path(book)
click_link "pledge-btn"
end
Unfortunately, the request to book_path(book) 404s because the book cannot be found.
If I take the :js flag off, the test passes.
I have DatabaseCleaner set up to use :truncation for JS specs as is the recommended method.
# spec/support/database_cleaner.rb
RSpec.configure do |config|
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
DatabaseMetadata.create!(:sanitized_at => DateTime.now)
end
config.after(:each) do
DatabaseCleaner.clean
end
end
I can puts the book in the test and it will be found.
it "should allow pledging to a Hardback level", js: true do
book = FactoryGirl.create :book
visit book_path(book)
p Book.first
# => .. the book instance
click_link "pledge-btn"
end
I've also tried this shared connection method which doesn't seem to fix the problem either.
What else could be wrong?
You may have config.use_transactional_fixtures = true set in your spec_helper.rb. This would override what you have above.
You want to either remove this line from your spec_helper.rb or change it there to be false.
I ran into this same issue and had a very similar config. After looking through the DatabaseCleaner README, I found this small note:
It's also recommended to use append_after to ensure DatabaseCleaner.clean runs after the after-test cleanup capybara/rspec installs.
Source
That means changing your
config.after(:each) do
DatabaseCleaner.clean
end
to
config.append_after(:each) do
DatabaseCleaner.clean
end
Note the append_after instead of after. This fixed my problem.
Working on legacy project I have had such issue, it was caused by switching DatabaseCleaner strategy to :truncation like the following:
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
DatabaseCleaner.clean
Test::Seeder.seed!
DatabaseCleaner.strategy = :transaction
end
so, removing DatabaseCleaner.strategy = :transaction helped in my case
If you create records in a before(:all) block then they will be available.
before :all do
#book = FactoryGirl.create :book
end
it "should allow pledging to a Hardback level", js: true do
visit book_path(#book)
click_link "pledge-btn"
end
Capybara runs the rails server in a separate process from the tests, so they each get their own connection to the database. Therefore, the server does not access to the records created in the transaction for the test.
Because they are not inside a transaction, make sure that you clean them up with DatabaseCleaner in your spec_helper.rb:
config.after(:all, :type => :feature) do
DatabaseCleaner.clean
end
For anyone that lands here in 2019 and beyond, I was caught out by the following code which I copied verbatim from the DatabaseCleaner readme:
config.before(:each, type: :feature) do
# :rack_test driver's Rack app under test shares database connection
# with the specs, so continue to use transaction strategy for speed.
driver_shares_db_connection_with_specs = Capybara.current_driver == :rack_test
unless driver_shares_db_connection_with_specs
# Driver is probably for an external browser with an app
# under test that does *not* share a database connection with the
# specs, so use truncation strategy.
DatabaseCleaner.strategy = :truncation
end
end
This is all well and good, but I am using Rails system specs, not RSpec features and therefore this code block was never being run.
Change config.before(:each, type: :feature) to config.before(:each, type: :system) if you're using system specs!
I think your main issue is that your rails_helper.rb has the following line commented out:
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
This means your database_cleaner.rb is never getting loaded.
I have a controller sending in a list of vendors to my controller, and on normal view it's working fine.
class VendorsController < ApplicationController
respond_to :html, :json
def index
#vendor_categories = VendorCategory.where(:is_top_level => true)
#vendors = Vendor.includes(:vendor_tier, :vendor_categorizations, :vendor_categories).order('vendor_tier_id DESC, name ASC')
respond_with #vendors
end
end
In my view I have the following two lines:
= debug #vendors
= debug current_user.user_vendor_choices
which, again, are working if I view it in the browser. However, if I test it with Capybara and RSpec, it's empty.
require 'spec_helper'
describe 'Vendors' do
before do
category = create(:vendor_category)
5.times do
vendor = create(:vendor)
vendor_categorization = create(:vendor_categorization, vendor: vendor, vendor_category: category)
p vendor
p category
p vendor_categorization
end
visit signup_path
#new_user = sign_up
end
before(:each) do
visit destroy_user_session_path
visit new_user_session_path
sign_in #new_user
visit vendors_path
end
it 'should save selected vendors', js: true do
p Vendor.includes(:vendor_tier, :vendor_categorizations, :vendor_categories).order('vendor_tier_id DESC, name ASC').count
end
end
Vendor.all and the above Vendor.includes... both return values, but for some reason in my test it's not showing anything... getting a Capybara::Element not found.
UPDATE
For testing purposes, I created the Vendors directly with the controller:
def index
#vendor_categories = VendorCategory.where(:is_top_level => true)
4.times do
Vendor.create({name: 'Test McTesterson', vendor_tier_id: 1})
end
#vendors = Vendor.includes(:vendor_tier, :vendor_categorizations, :vendor_categories).order('vendor_tier_id DESC, name ASC')
respond_with #vendors
end
Spec passes. What the--? This must be a FactoryGirl issue, or for some reason my records are deleted before it can run the test? Consoling the objects after I create them is showing a record with an ID, which I guess doesn't prove that it's putting them in the database...
Turns out my Database Cleaner activities defined in my spec_helper were a little too vigorous. I had:
RSpec.configure do |config|
config.use_transactional_fixtures = false
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end
I had to get rid of the second chunk, so it now reads:
RSpec.configure do |config|
config.use_transactional_fixtures = false
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
end
And it works! Not really sure why... any ideas (aside from the obvious, before it was calling database cleaner before/after each test)?
Hi I cursorily glanced at this question, not sure you even need the help anymore, but I think the reason this is failing is a fundamental set up issue that your answer is just patching around.
When you're running a js: true spec (by the way, js: true should be on the describe line, not the it line), short version, Capybara works in different threads, so instance variables created in a before block, unlike with regular Rspec testing, are not available in the spec. To make them available, you have to use a truncation cleaning strategy.
RSpec.configure do |config|
config.use_transactional_fixtures = false
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
TL;DR when running a js test, truncation is basically required (unless obviously you're running js tests that don't require any database interactions). When running all other tests, use transactions (because it's also much faster). I guess your answer replicated this to some extent =)
I am trying to refactor some RSpec/Rails tests so that they persist as few objects to the database as possible, but am having trouble trying to figure out how to re-write tests like the following:
describe User do
context "record creation" do
before(:each) { #user = User.new(user_atts) }
it "should generate a confirmation_token" do
# Generated as the result of a callback
#user.save!
expect(#user.confirmation_token).to be_present
end
it "should set the confirmed_at attribute to nil" do
# Cleared as the result of a callback
#user.save!
expect(#user.confirmed_at).to be_nil
end
it "should call the send_confirmation_instructions method" do
#user.should_receive(:send_confirmation_instructions) {}
#user.save!
end
end
def user_atts
# return attributes hash
end
end
This is a pretty simple example, but there are plenty of similar instances in my specs, and, for the most part, they all persist records to the database. I would love to take advantage of RSpec's let and subject helpers, but am not fully sure that those would even help here.
I have been using FactoryGirl a lot and thought that maybe its build_stubbed strategy would speed up my specs a bit, but I couldn't find many instances where it would help limit actual record creation (or maybe I don't know how to use).
I assume there are some cases where a test requires record creation, but the above example hardly seems like one of them. Should I even be trying to refactor this or is there a better to write these tests? Any help would be greatly appreciated.
My tests would probably look something like this.
describe User do
let(:user) { FactoryGirl.build_stubbed(:user) }
context "record creation" do
it "should generate a confirmation_token" do
user.save!
expect(user.confirmation_token).to be_present
end
it "should set the confirmed_at attribute to nil" do
user.save!
expect(user.confirmed_at).to be_nil
end
it "should call the send_confirmation_instructions method" do
expect(user).to receive(:send_confirmation_instructions).once
user.save!
end
end
end
That's using Factory Girl to create the user models. Also, I'd have DatabaseCleaner to clear the database after each test as stated by #RahulGarg
All you'd have to do is configure in your spec_helper something like this
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
This means after each test the Database would be cleared.