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) }
Related
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
Its my factorygirl code
FactoryGirl.define do
factory :account do
sequence :name do |n|
"Test Account#{n}"
end
end
end
This is my method for run factorygirl code
def create_accounts n=2
n.times do
FactoryGirl.create(:account, subscription_ids: #sub.id.to_s)
end
end
My problem is, first time my FactoryGirl output is Test Account1, Test Account2, When i execute second time, It create output as Test Account3, Test Account4. But I need Test Account1, Test Account2 when run multiple time. How may i do this.
Thanks for your advices
FactoryGirl is designed to create new unique records every time you call #create. If you want to keep the original record set around, you should save them to a variable and then return them rather than running FactoryGirl.create again.
You can also use database_cleaner gem to clean the database after every test. This helps to prevent any problems rising from the state of the database.
I solve this problem after replace this code
def create_accounts n=1
create_subscription
n.times do |r|
r += 1
FactoryGirl.create(:account, subscription_ids: #sub.id.to_s, name: "Test Account#{r}")
end
end
Updated
I am using cucumber-> capybara -> selenium
Reset the factory girl sequence
add this code in spec->support->reset.rb
module FactoryGirl
def self.reload
self.factories.clear
self.sequences.clear
self.traits.clear
self.find_definitions
end
end
Add this in env.rb
After do
FactoryGirl.reload
end
I'm new to using RSpec for writing tests in a Rails application which uses a MySQL database. I have defined my fixtures and am loading them in my spec as follows:
before(:all) do
fixtures :student
end
Does this declaration save the data defined in my fixtures in the students table or does it just load the data in the table while the tests are running and remove it from the table after all the tests are run?
If you want to use fixtures with RSpec, specify your fixtures in the describe block, not within a before block:
describe StudentsController do
fixtures :students
before do
# more test setup
end
end
Your student fixtures will get loaded into the students table and then rolled back at the end of each test using database transactions.
First of all: You cannot use method fixtures in :all / :context / :suite hook. Do not try to use fixtures in these hooks (like post(:my_post)).
You can prepare fixtures only in describe/context block as Infuse write earlier.
Call
fixtures :students, :teachers
do not load any data into DB! Just prepares helper methods students and teachers.
Demanded records are loaded lazily in the moment when You first try to access them. Right before
dan=students(:dan)
This will load students and teachers in delete all from table + insert fixtures way.
So if you prepare some students in before(:context) hook, they will be gone now!!
Insert of records is done just once in test suite.
Records from fixtures are not deleted at the end of test suite. They are deleted and re-inserted on next test suite run.
example:
#students.yml
dan:
name: Dan
paul:
name: Paul
#teachers.yml
snape:
name: Severus
describe Student do
fixtures :students, :teachers
before(:context) do
#james=Student.create!(name: "James")
end
it "have name" do
expect(Student.find(#james.id)).to be_present
expect(Student.count).to eq 1
expect(Teacher.count).to eq 0
students(:dan)
expect(Student.find_by_name(#james.name)).to be_blank
expect(Student.count).to eq 2
expect(Teacher.count).to eq 1
end
end
#but when fixtures are in DB (after first call), all works as expected (by me)
describe Teacher do
fixtures :teachers # was loaded in previous tests
before(:context) do
#james=Student.create!(name: "James")
#thomas=Teacher.create!(name: "Thomas")
end
it "have name" do
expect(Teacher.find(#thomas.id)).to be_present
expect(Student.count).to eq 3 # :dan, :paul, #james
expect(Teacher.count).to eq 2 # :snape, #thomas
students(:dan)
expect(Teacher.find_by_name(#thomas.name)).to be_present
expect(Student.count).to eq 3
expect(Teacher.count).to eq 2
end
end
All expectations in tests above will pass
If these test are run again (in next suite) and in this order, than expectation
expect(Student.count).to eq 1
will be NOT met! There will be 3 students (:dan, :paul and fresh new #james). All of them will be deleted before students(:dan) and only :paul and :dan will be inserted again.
before(:all) keeps the exact data around, as it's loaded/created once. You do your thing, and at the end of the test it stays. That's why bui's link has after(:all) to destroy or use before(:each); #var.reload!;end to get the latest data from the tests before. I can see using this approach in nested rspec describe blocks.
In my model, I have to choose an asset, saved in a editorial_asset table.
include ActionDispatch::TestProcess
FactoryGirl.define do
factory :editorial_asset do
editorial_asset { fixture_file_upload("#{Rails.root}/spec/fixtures/files/fakeUp.png", "image/png") }
end
end
so I have attached in my model factory an association on :editorial_asset
Upload work great, but take too much time (1s per example)
I'm wonder if it's possible to create uploads one time before each examples, and say in the factory: "find instead of create"
But the problem with database_cleaner, I cannot except tables with :transaction, truncation take 25sec instead of 40ms !
EDIT
The factory that need an asset
FactoryGirl.define do
factory :actu do
sequence(:title) {|n| "Actu #{n}"}
sequence(:subtitle) {|n| "Sous-sitre #{n}"}
body Lipsum.paragraphs[3]
# Associations
user
# editorial_asset
end
end
The model spec
require 'spec_helper'
describe Actu do
before(:all) do
#asset = create(:editorial_asset)
end
after(:all) do
EditorialAsset.destroy_all
end
it "has a valid factory" do
create(:actu).should be_valid
end
end
So a working way is
it "has a valid factory" do
create(:actu, editorial_asset: #asset).should be_valid
end
but there's no way to inject automatically association ?
Since you're using RSpec, you could use a before(:all) block to set up these records once. However, anything done in a before-all block is NOT considered part of the transaction, so you will have to delete anything from the DB yourself in an after-all block.
Your factory for the model that has an association to the editorial asset could then, yes, try to first find one before creating it. Instead of doing something like association :editorial_asset you could do:
editorial_asset { EditorialAsset.first || Factory.create(:editorial_asset) }
Your rspec tests could then look like this:
before(:all) do
#editorial = Factory.create :editorial_asset
end
after(:all) do
EditorialAsset.destroy_all
end
it "already has an editorial asset." do
model = Factory.create :model_with_editorial_asset
model.editorial_asset.should == #editorial
end
Read more about before and after blocks on the Rspec GitHub wiki page or on the Relish documentation:
https://github.com/rspec/rspec-rails
https://www.relishapp.com/rspec
I'm writing integration tests using Rspec and Capybara. I've noticed that quite often I have to execute the same bits of code when it comes to testing the creation of activerecord options.
For instance:
it "should create a new instance" do
# I create an instance here
end
it "should do something based on a new instance" do
# I create an instance here
# I click into the record and add a sub record, or something else
end
The problem seems to be that ActiveRecord objects aren't persisted across tests, however Capybara by default maintains the same session in a spec (weirdness).
I could mock these records, but since this is an integration test and some of these records are pretty complicated (they have image attachments and whatnot) it's much simpler to use Capybara and fill out the user-facing forms.
I've tried defining a function that creates a new record, but that doesn't feel right for some reason. What's the best practice for this?
There are a couple different ways to go here. First of all, in both cases, you can group your example blocks under either a describe or context block, like this:
describe "your instance" do
it "..." do
# do stuff here
end
it "..." do
# do other stuff here
end
end
Then, within the describe or context block, you can set up state that can be used in all the examples, like this:
describe "your instance" do
# run before each example block under the describe block
before(:each) do
# I create an instance here
end
it "creates a new instance" do
# do stuff here
end
it "do something based on a new instance" do
# do other stuff here
end
end
As an alternative to the before(:each) block, you can also use let helper, which I find a little more readable. You can see more about it here.
The very best practice for your requirements is to use Factory Girl for creating records from a blueprint which define common attributes and database_cleaner to clean database across different tests/specs.
And never keep state (such as created records) across different specs, it will lead to dependent specs. You could spot this kind of dependencies using the --order rand option of rspec. If your specs fails randomly you have this kind of issue.
Given the title (...reusing code in Rspec) I suggest the reading of RSpec custom matchers in the "Ruby on Rails Tutorial".
Michael Hartl suggests two solutions to duplication in specs:
Define helper methods for common operations (e.g. log in a user)
Define custom matchers
Use these stuff help decoupling the tests from the implementation.
In addition to these I suggest (as Fabio said) to use FactoryGirl.
You could check my sample rails project. You could find there: https://github.com/lucassus/locomotive
how to use factory_girl
some examples of custom matchers and macros (in spec/support)
how to use shared_examples
and finally how to use very nice shoulda-macros
I would use a combination of factory_girl and Rspec's let method:
describe User do
let(:user) { create :user } # 'create' is a factory_girl method, that will save a new user in the test database
it "should be able to run" do
user.run.should be_true
end
it "should not be able to walk" do
user.walk.should be_false
end
end
# spec/factories/users.rb
FactoryGirl.define do
factory :user do
email { Faker::Internet.email }
username { Faker::Internet.user_name }
end
end
This allows you to do great stuff like this:
describe User do
let(:user) { create :user, attributes }
let(:attributes) { Hash.new }
it "should be able to run" do
user.run.should be_true
end
it "should not be able to walk" do
user.walk.should be_false
end
context "when user is admin" do
let(:attributes) { { admin: true } }
it "should be able to walk" do
user.walk.should be_true
end
end
end