Weird behaviour in Rspec - ruby-on-rails

I am running some test in my controller spec and I found a weird behaviour I cannot explain why this is happen.
My Spec look like this:
require 'rails_helper'
describe BooksController do
let(:user_with_books) { create :user, :with_books }
...
describe 'GET /books/:book_id/owners' do
it 'shows all owners of the book' do
book = user_with_books.books.first
user_2 = create :user
book.owners << user_2
get :owners, book_id: user_with_books.books.first.id
expect(assigns(:users).count).to eq 2
expect(assigns(:users).first).to eq user_with_books
expect(assigns(:users).second).to eq user_2
end
end
end
If I run the command
rspec spec/controllers/books_controller_spec.rb:31 everything is green:
But if I run just rspec, the test will fail!
What is Rspec doing on this spec to change the behaviour? What can I do to fix this issue?
EDIT: My spec_helper.rb
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
config.infer_spec_type_from_file_location!
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
end

According to your spec_helper.rb, you are never calling DatabaseCleaner.clean. It should be called to actually clean the database up in, e. g. before(:each) filter:
config.before(:each) do
DatabaseCleaner.clean
end

Related

RSpec: FactoryBot is seeing duplicate definitions

I'm still new to using FactoryBot so I might be missing something. I am getting this error message:
Could it be due to improper set up in the spec_helper.rb file?
As for defining the user.rb factory, I tried including "associations: contracts" in the user.rb file. I'm still not sure if I should be doing that or is this current format fine for Rspec to pick up the association with contracts.rb?
Any help is appreciated! Thanks!
spec_helper.rb
require 'factory_bot_rails'
RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
FactoryBot.definition_file_paths = [File.expand_path('../factories', __FILE__)]
FactoryBot.find_definitions
# rspec-expectations config goes here. You can use an alternate
# assertion/expectation library such as wrong or the stdlib/minitest
# assertions if you prefer.
config.expect_with :rspec do |expectations|
spec/factories/users.rb
FactoryBot.define do
factory :user do
full_name "Test tester"
email "test#tester.com"
password "123456"
end
end
spec/factories/contracts.rb
FactoryBot.define do
factory :contract do
vendor "O2"
starts_on "2019-03-08"
ends_on "2019-03-10"
price 30
end
end
spec/requests/contracts_api_spec.rb
require 'rails_helper'
RSpec.describe "ContractsApi", type: :request do
describe "POST #create" do
before(:each) do
#user = FactoryBot.create(:user)
#current_user = AuthenticateUserCommand.call(#user.email, #user.password)
#contract = #current_user.contracts.create(vendor: "Lebara", starts_on: "2018-12-12", ends_on: "2018-12-14", price: "15")
end
it 'creates a new contract' do
expect { post api_v1_contracts_path, params: #contract }.to change(Contract, :count).by(1)
end
end
end
I believe you don't need to configure FactoryBot in your spec_helper.rb and what you are doing there maybe be causing FactoryBot to load the factories twice.
Try changing the content of spec_helper.rb to just:
RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
# rspec-expectations config goes here. You can use an alternate
# assertion/expectation library such as wrong or the stdlib/minitest
# assertions if you prefer.
config.expect_with :rspec do |expectations|
Also, considering that you are including FactoryBot::Syntax::Methods, in your tests you can simply use #user = create(:user) instead of #user = FactoryBot.create(:user)

Rails 4 rspec 3 controller test: session helper module not working for before(:all), works for before(:each)

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

Capybara can't find database records during feature specs

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.

rspec DatabaseCleaner skip clean for example group metadata tag

How can I tag an example group so that the database isn't cleaned between each example, but is cleaned before and after the whole group? And untagged specs should clean the database between each example.
I would like to write:
describe 'my_dirty_group', :dont_clean do
...
end
So in my spec_helper.rb I put:
config.use_transactional_fixtures = false
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
end
config.before(:suite, dont_clean: true) do
DatabaseCleaner.clean
end
config.after(:suite, dont_clean: true) do
DatabaseCleaner.clean
end
config.before(:each, dont_clean: nil) do
DatabaseCleaner.start
end
config.before(:each, dont_clean: nil) do
DatabaseCleaner.clean
end
The problem is that the dont_clean: nil (or false) blocks in spec_helper don't run when the metadata tag is not specified. Is there another way to check for presence of :dont_clean before cleaning between examples?
Summary
You can set custom metadata on the whole example block, and then access the metadata within your RSpec config with self.class.metadata for use with conditional logic.
Code
Using these gem versions:
$ bundle exec gem list | grep -E '^rails |^rspec-core |^database'
database_cleaner (1.4.0)
rails (4.2.0)
rspec-core (3.2.0)
The following works for me:
File: spec/spec_helper.rb
RSpec.configure do |config|
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
DatabaseCleaner.clean_with(:truncation)
end
config.before(:all) do
# Clean before each example group if clean_as_group is set
if self.class.metadata[:clean_as_group]
DatabaseCleaner.clean
end
end
config.after(:all) do
# Clean after each example group if clean_as_group is set
if self.class.metadata[:clean_as_group]
DatabaseCleaner.clean
end
end
config.before(:each) do
# Clean before each example unless clean_as_group is set
unless self.class.metadata[:clean_as_group]
DatabaseCleaner.start
end
end
config.after(:each) do
# Clean before each example unless clean_as_group is set
unless self.class.metadata[:clean_as_group]
DatabaseCleaner.clean
end
end
end
File: spec/models/foo_spec.rb
require 'spec_helper'
describe 'a particular resource saved to the database', clean_as_group: true do
it 'should initially be empty' do
expect(Foo.count).to eq(0)
foo = Foo.create()
end
it 'should NOT get cleaned between examples within a group' do
expect(Foo.count).to eq(1)
end
end
describe 'that same resource again' do
it 'should get cleaned between example groups' do
expect(Foo.count).to eq(0)
foo = Foo.create()
end
it 'should get cleaned between examples within a group in the absence of metadata' do
expect(Foo.count).to eq(0)
end
end
You can check on example.metadata inside the blocks, although I haven't been able to figure out how to do this for before(:suite)

Is it possible to add "somewhere" a `before(:each)` hook so that all spec file can run it?

I am using Ruby on Rails 3.2.2 and rspec-rails-2.8.1. In order to make my spec files DRY (Don't Repeat Yourself) and to seed the test database I would like to run a before(:each) hook for all those spec files. That is, in all my spec files I have the following code:
describe 'test description' do
before(:each) do
load "#{Rails.root}/db/seeds.rb"
end
...
end
Is it possible to add "somewhere" that before(:each) hook so that all spec files can run it? What do you advice?
In the spec_helper.rb:
RSpec.configure do |config|
#your other config
config.before(:each) do
#your code here
end
end
There is much configuration available. For instance: config.before(:each, :type => [:routing, :controller, :request])
You can even create your own tags and associate code to it:
config.around :each, unobstrusive: true do |example|
Capybara.current_driver = :rack_test
example.run
Capybara.current_driver = :selenium
end
You can add before/after hooks in your Rspec.configure block, usually in your spec_helper:
RSpec.configure do |config|
config.before(:each) do
...
end
end

Resources