why factoryGirl don't rollback after build and save in rspec - ruby-on-rails

I don't understand why my record in 2 spec is not rollback:
I have 2 tables, prefecture has_many hospitals
require "rails_helper"
RSpec.describe Hospital, type: :model, focus: true do
context "created along with new hospital created" do
it "should create new hospital" do
#prefecture = FactoryGirl.create(:prefecture)
hospital = FactoryGirl.build(:hospital, id: 10, prefecture: #prefecture)
expect { hospital.save }.to change { Hospital.count }.by 1
end
it "should save" do
#prefecture = FactoryGirl.create(:prefecture)
hospital = FactoryGirl.build(:hospital, id: 10, prefecture: #prefecture)
hospital.save
end
end
end
If I run it will show error "id=10 is existed in db"
Can anyone explain where I am mistake?
Thank you.

It is not the responsibility of FactoryGirl. You have 2 options:
Use transactions in tests: https://relishapp.com/rspec/rspec-rails/docs/transactions
Use a gem which cleans DB after after test such as: https://github.com/DatabaseCleaner/database_cleaner
Usually your models test will use transctions and database cleaner is used for integration/acceptance tests which cannot operate within transaction because a separate process (browser) needs to read the data created in tests.

By default, the rspec will not reset your database for each test. You need to configure it. Is because that your second test is falling, already exists a Hospital with id = 10.
When you remove the parameter id for the second test and runs with success, FactoryGirl will generate automatically a new id.
The method build from FactoryGirl will not fill the attribute id, just the rest of the attributes. Is because that when you call save after build method, the FactoryGirll generate a new id and your tests pass successfully.

after :build do |offer|
offer.save
end

Related

Rspec - Running setup for many examples in one context only once

I have just finished reading Testing Rails guide by thoughtbot. The author mentions that using let! or let is an antipattern because in your test you dont get to see the variables that are needed for the setup.
I have this one common case that I'm curious how to deal with however if lets would be removed. I'm creating tests for creating a cart. When a cart is created, many things are tested. The current test is as follows (I removed the implementation of some methods and just kept the structure for brevity):
RSpec.describe Mutations::Carts::CreateCart, type: :request do
context 'Sending a request with an unauthorized user' do
it 'Creates a new cart and set the table for it' do
# Test setup
store = create :store
branch = store.branches.first
table = create :table, branch: branch
create_dependencies_for_create_cart(store)
cart_count_before_request = Cart.count
# Exercise
send_create_cart(store.id, table.id)
# Verify: (Some expects)
end
it 'Returns the branch id and and the table id' do
# Test setup
store = create :store
branch = store.branches.first
table = create :table, branch: branch
create_dependencies_for_create_cart(store)
# Exercise
send_create_cart(store.id, table.id)
# Verify: Some expects
end
end
private
def send_create_cart(store_id, table_id)
# Prepare and send the request
end
def create_dependencies_for_create_cart(store)
# Creating some objects that need to be created before the cart is created
end
end
My problem here is that the test setup is common and needs to only run once. Also the request can be sent once.
One tiny improvement over this would be to add a before(:each). This would on the one hand DRY the code a bit, but on the other hand, I'm still running the setup + exercise twice potentially slowing my tests down, and I'm making the test less readable. before(:all) would at least ensure that the setup and exercise are only run once, still this is deperacted and recommended against by the Ruby community.
What's the clean and effecient way (both readable and requires no repeated setups) to test such cases?
All create should be set via let. Keep in mind that all your specs must be independent all the time. No matter the order they have been run.
If you've a test that checks if a new cart has be created. That's one test. Another one might be to check the response.
Even if best practices say it's better to have a single expect per spec, sometime it makes sense to have several. It's all about context and logical for other dev to debug your code later on.
Here is my proposition:
context 'Sending a request with an unauthorized user' do
subject { send_create_cart(store.id, table.id) }
let(:store) { create(:store) }
let(:branch) { create(:branch, store: store)} # check according to your association
let!(:table) { create(:table, branch: branch) }
# Create dependencies for cart here, on `let` too.
# use `!` to force creation
# Here only `table` has `!` since other variable are called to create this one.
it 'Creates a new cart and set the table for it' do
expect{ subject }.to change { Cart.count }.by(1)
end
it 'Returns the branch id and and the table id' do
expect(subject).to eq([branch.id, table.id])
end
private
def send_create_cart(store_id, table_id)
# Prepare and send the request
end
end
A good approach would be using a describe with a context with a different describes inside it each having its own before do.
describe 'Sending a request' do
context ' with an unauthorized user' do
store = create :restaurant
branch = store.branches.first
table = create :table, branch: branch
describe 'creating cart and table' do
before do
create_dependencies(store)
cart_count_before_request = Cart.count
# Exercise
send_create_cart()
end
it 'should not create a cart' do
# Verify: (Some expects)
end
it 'returns the ids'do
# Verify: Some expects
end
end
end
end

About transaction using in RSpec test

I met a very strange issue when writing test using RSpec. Assume that I have 2 models: Company and Item with association company has_many items. I also set up database_cleaner with strategy transaction. My RSpec version is 2.13.0, database_cleaner version is 1.0.1, rails version is 3.2.15, factory_girl version is 4.2.0. Here is my test:
let(:company) { RSpec.configuration.company }
context "has accounts" do
it "returns true" do
company.items << FactoryGirl.create(:item)
company.items.count.should > 0
end
end
context "does not have accounts" do
it "returns false" do
company.items.count.should == 0
end
end
end
I set up an initial company to rspec configuration for using in every test because I don't want to recreate it in every tests because creating a company takes a lot of time(due to its callbacks and validations). The second test fails because item is not cleaned from the database after the first test. I don't understand why. If I change line company.items << FactoryGirl.create(:item) to FactoryGirl.create(:item, company: company), the it passes. So can any body explain for me why transaction isn't rollbacked in the first situation. I'm really messed up
Thanks. I really appreciate.
I think the problem is not in the rollback and I'm wondering if company.items can store it's value between contexts but I'm not sure.
I'm unable to reproduce it quickly so I want to ask you to:
check log/test.log when the rollback is performed
how many INSERTs was made for company.items << FactoryGirl.create(:item)
than change on the first test > to < that way: company.items.count.should < 0 it'll make test to fail but you'll get count value. Is it 1 or 2 ?
If you have relation between Company and Item model like has_many/belongs_to than I would suggest to just use build(:item) which should create company for it as well:
for example:
let(:item) { FactoryGirl.build(:item) }
context "has accounts"
it "returns true" do
item.save
Company.items.count.should == 1
end
don't forget to include association :company line at :item's factory
Hint:
add to spec_helper.rb:
RSpec.configure do |config|
# most omitted
config.include FactoryGirl::Syntax::Methods
and you can call any FactoryGirl method directly like this:
let(:item) { build(:item) }

Rspec/FactoryGirl: clean database state

I am new to Rspec and Factory girl and would like my test to run on a specific database state. I understand I can get Factory girl to create these records, and the objects will be destroyed after the test run, but what happens if I have data in the database.
For example: I want my test to run when there are 3 records in the database that I created through Factory Girl. However, I currently already have 1 model record in the database, and I don't want to delete it just for the test. Having that 1 model in there ruins my test.
Database Content
[#<Leaderboard id: 1, score: 500, name: "Trudy">]
leaderboard_spec.rb
require 'spec_helper'
describe Rom::Leaderboard do
describe "poll leaderboard" do
it "should say 'Successful Run' when it returns" do
FactoryGirl.create(:leaderboard, score: 400, name: "Alice")
FactoryGirl.create(:leaderboard, score: 300, name: "Bob")
FactoryGirl.create(:leaderboard, score: 200, name: "John")
Leaderboard.highest_scorer.name.should == "Alice"
end
end
end
Now my test will fail because it will incorrectly assume that Trudy is the highest scorer, since the test have run in an incorrect state.
Does factory girl offer anyway to delete records from the database then rollback this delete? Similar to how it creates records in the database and rollsback
Its popular to use the database_cleaner gem. You can find it here:
https://github.com/bmabey/database_cleaner
The documentation recommends the following configuration for rspec:
RSpec.configure do |config|
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
This will you make sure you have a clean database for each test.
To answer your rollback question as directly as possible: no there isn't a way to rollback a delete within a test.
Following test conventions your goal usually is to start with a clean slate, and use factory_girl to efficiently build the scenario in the database you need to test for.
You can accomplish what you want by, for instance, adding a this to your leaderboards.rb factory file:
factory :trudy do
id 1
score 500
name "Trudy"
end
Or you could create a simple helper function in your test file that regenerates that record when its needed for tests:
def create_trudy
FactoryGirl.create :leaderboard,
id: 1,
score: 500,
name: "Trudy"
end
end
Or you could place all this in a before(:suite) within a describe block, like so:
describe "with a leaderboard record existing" do
before(:each) do
FactoryGirl.create :leaderboard, id: 1, score: 500, name: "Trudy"
end
# Tests with an initial leaderboard record
end
describe "with no leaderboard records initially" do
# Your test above would go here
end
In this final suggestion, your tests become very descriptive and when viewing the output, you will know exactly what state your database was in at the beginning of each test.

Only one expectation per it {} block: So how to improve this spec?

My Rails models: task has_many positions.
Scenario: When I create a new position, it should create itself a task. I'd like to test that, and I'm doing it like this:
context "creating a new position" do
let(:position) { create :position, name: 'Read some books', :task => nil }
it "should create a simple task" do
Task.find_by_name('Read some books').should be_nil # First should
position # Execute let() block (FactoryGirl is lazy evaluating)
Task.find_by_name('Read some books').should_not be_nil # Second (more relevant) should
end
end
So how should I improve my test? The first "should" simply makes sure that there isn't already a Task, so we can be sure that creating the Position creates the Task. But this violates the "only one should per it block" principle. So what about this?
context "creating a new position" do
let(:position) do
position = create :position, name: 'Read some books', :task => nil
Task.delete_all
position
end
it "should create a simple task" do
position # Execute let() block (FactoryGirl is lazy evaluating)
Task.find_by_name('Read some books').should_not be_nil
end
end
Or should I simply count on the fact that there shouldn't be such a task anyways (because a clean test db wouldn't have one)? Thanks for your opinions.
Update (Solution)
After some research I found the change matcher of RSpec:
let(:position) { create :position, name: 'Read some books', :task => nil }
it "should create a simple task" do
# Thanks to FactoryGirl's lazy evaluation of let(), the position doesn't yet exist in the first place, and then after calling position in the expect{} block, it is created.
expect { position }.to change{ Task.count(conditions: { name: 'Read some books' }) }.by(1)
end
What to Test
I will not address in detail whether the tests themselves are useful to any degree. To me, they seem to be exercising basic database functions rather than application logic, which is of marginal utility, but only you can really decide what's important to test.
Be Specific
In the example you give, there's no real reason to use a let block, which memoizes the variable. If only one test needs the record, instantiate it just in that specific test. For example:
context 'creating a new position' do
it 'should be nil when the position record is missing' do
Task.find_by_name('Read some books').should be_nil
end
it 'should successfully create a position' do
create :position, name: 'Read some books', :task => nil
Task.find_by_name('Read some books').should_not be_nil
end
end
Alternatively, if you're trying to test how your application behaves when a record is missing, then go ahead and memoize a variable or create a record in a before block, but explicitly delete the record in that one specific test.
Multiple Contexts
Finally, if you're finding that you have too much state to set up in individual tests, that's usually a clue that you should consider splitting your tests into different contexts. For example, you might want to separate tests into one context that checks behavior when a record doesn't exist, and a separate context for when records do exist.
Like all things testing, it's an art more than a science. Your mileage may vary.
RSpec 2.11 allows you to pass a block to change, and it expects the return value of the block to be the thing that changes. I would expect this to work for you:
expect { position }.to change { Task.where(:name => 'Read some books').count }.from(0).to(1)

Ruby on Rails Test Database Not Committing New Records

Is there a way to use Factory Girl to commit new changes to the test database in Ruby on Rails?
I have the following factory:
Factory.define :shipping_info do |si|
si.name "Foo Bar"
si.street "12 Candy Lane"
si.country "US"
si.zip "12345"
si.state "IL"
end
And I have the the following test:
require File.dirname(__FILE__) + '/../../spec_helper'
describe CgCart::ShippingInfo do
before(:each) do
#order = Factory(:order)
#order.order_line_item = Factory(:order_line_item)
#shipping_info = Factory(:shipping_info)
end
it "order should not be nil" do
#shipping_info.should_not be_nil
end
end
When I run this test it passes. However, no new records are created in my test database. I need data in there for a Dameon to work with.
Any suggestions?
Using Factory.create(:order) should write that object to your database.
If you have transactional fixture set to true, all data that gets written to database gets rolled back after the tests finish.
You cant tail -f log/test.log before running your tests and you will see a lot of activity there.
I hope that clears your situation.

Resources