Rspec/FactoryGirl uniqueness validations in a large test suite - ruby-on-rails

Using Rspec and FactoryGirl, if I have a factory that autoincrements a trait using a sequence, and in some specs if I explicitly set this trait, with a large enough test suite, sometimes random specs fail with
Validation failed: uniq_id has already been taken
The factory is defined like this:
factory :user { sequence(:uniq_id) {|n| n + 1000} }
I'm guessing this validation fails because in one place in my test suite, I generate a user like this:
create(:user, uniq_id: 5555)
And because presumably factory girl is generating more than 4,555 users over the suite, the validation is failing?
I'm attempting to avoid this problem by just turning the uniq_id into 55555 (larger number), so there is no interference. But is there a better solution? My spec_helper includes these relevant bits:
config.use_transactional_fixtures = true
config.after(:all) do
DatabaseCleaner.clean_with(:truncation)
end

It happens to me sometimes. I didn't found any explanation, but happens only with big set of data. I let someone find the explanation!
When it happens, you can declare your attribute like this (here is an example using faker gem) :
FactoryGirl.define do
factory :user do
login do
# first attempt
l = Faker::Internet.user_name
while User.exists?(:login => l) do
# Here is a loop forcing validation
l = Faker::Internet.user_name
end
l # return login
end
end
end

I was able to solve my issue like this in my factory (based on #gotva's suggestion in the question comments).
factory :user do
sequence(:uniq_id) { |n| n + 1000 }
# increment again if somehow invalid
after(:build) do |obj|
if !obj.valid? && obj.errors.keys.include?(:uniq_id)
obj.uniq_id +=1
end
end
end

Related

Duplicate associations in Factory Girl

I am using fixtures to load test data on a Ruby on Rails project. I moved to Factory Girl, but I am getting duplicate entries for associations.
I have a Group model and a Value model. A Group can have multiple Values.
Also, I am using Cucumber for my tests. And FactoryGirl.lint to populate the database.
My fixtures
groups.yml
group_1:
name: "Flavour"
values.yml
value_1:
name: "Strawberry"
group: group_1
value_2:
name: "Mint"
group: group_1
value_3:
name: "Chocolate"
group: group_1
This works just fine. A single Group is created and the 3 Values are attached to it.
My factories
groups.rb
FactoryGirl.define do
factory :group do
name "Flavour"
end
end
values.rb
FactoryGirl.define do
factory :value_1, class: :value do
name "Strawberry"
group
end
factory :value_2, class: :value do
name "Mint"
group
end
factory :value_3, class: :value do
name "Chocolate"
group
end
end
This is not working. Factory Girl is creating 3 Groups, each one associated to 1 Value.
Is this normal behaviour for Factory Girl?
Also, I read that use_transactional_fixtures should be set to true. This is already the case.
I don't think you really have grasped the conceptual differences between using fixtures and factories.
With fixtures you have these static object definitions that get chucked into the database on each test run. Its like your database has a "zero" state with a bunch of data already. Feels nice, warm and fuzzy (Oh I don't have to set everything up, so nice!) but its a horrible idea in reality since it masks any errors caused by an empty table!
With factories you define objects dynamically instead and create them when needed. You use a tool like database_cleaner to clean out any residual state between tests. You start each test with nothing.
FactoryGirl.lint does not populate the database. Its a linter that checks that your factory definitions are correct.
There is absolutely zero point in using factory_girl if you just create a bunch of fixtures with it. So instead you want to do something like:
FactoryGirl.define do
factory :value do
name { ["Strawberry", "Mint", "Chocolate"].sample }
group
end
end
One of the mental hurdles of going from fixtures to factories is you have to stop writing tests like if you where using fixture data:
describe "GET /users/:id" do
let!(:user){ FactoryGirl.create(:user) }
let(:json) { JSON.parse(response.body) }
before { get users_path(#user), format: :json }
it "has the correct name" do
# bad
expect(json["name"]).to eq "John Doe" # strong coupling to factory!
# good
expect(json["name"]).to eq user.name # we don't know what the factory generates.
end
end
If you have to have a test where a certain factory value must be known or if a factory needs to be associated to a certain object then do it explicitly instead of making a mess of your definitions.
let(:user) { FactoryGirl.create(:user, name: 'Max') }
let(:item) { user.items.create(FactoryGirl.attributes_for(:item)) }
it "has an awesome name" do
expect(user.name).to eq 'Max'
end
it "is associated" do
expect(item.user).to eq user
end
Your value factories are told to use the group factory for their group, and the group factory is told to make a new group with name 'Flavor'. What you want to do differently is that you want the group factory to return the existing 'Flavor' rather than a new one. Here is how to do that.
FactoryGirl.define do
factory :group, class: :group do
name 'Flavor'
initialize_with {Group.find_or_initialize_by(name: name)}
end
end
That is saying if you already have a group with desired name, use existing group. Written as above so that there is a default name, and you get the same 'use existing if present' behavior when passing in an alternate name.

Why is 'FactoryGirl.lint' giving InvalidFactoryError?

Long time reader but first time poster here on SO :)
For the last couple of days I've been setting up FactoryGirl.
Yesterday I changed some factories (mainly my User and Brand factories) by replacing:
Language.find_or_create_by_code('en')
With:
Language.find_by_code('en') || create(:language)
Because the first option creates a Language object with only the code attribute filled in; while the second uses the Language factory to create the object (and thus fills in all the attributes specified in the factory)
Now when I run my test it immediately fails on Factory.lint, stating my user (and admin_user) factories are invalid. Reverting the above code doesn't fix this and the stack trace provided by FactoryGirl.lint is pretty useless..
When I comment the lint function, my tests actually run fine without any issues.
When I manually create the factory in rails console and use .valid? on it, it returns true so I'm at a loss why lint considers my factories invalid.
My user factory looks like this:
FactoryGirl.define do
factory :user do
ignore do
lang { Language.find_by_code('en') || create(:language) }
end
login "test_user"
email "test_user#test.com"
name "Jan"
password "test1234"
password_confirmation "test1234"
role # belongs_to :role
brand # belongs_to :brand
person # belongs_to :person
language { lang } # belongs_to :language
factory :admin_user do
association :role, factory: :admin
end
end
end
Here the role, person and language factories are pretty straightforward (just some strings) but the brand factory shares the same language as the user thus I use the code in the ignore block so FactoryGirl doesn't create 2 'en' language entries in my database.
Anyone has some ideas why I'm getting this InvalidFactoryError and maybe provide some insights on how to debug this?
UPDATE 1
It seems this problem is caused by another factory..
I have a factory called user_var_widget where I link a specific widget with a user:
factory :user_solar_widget, :class => 'UserWidget' do
sequence_number 2
user { User.find_by_login('test_user') } # || create(:user) }
widget { Widget.find_by_type('SolarWidget') || create(:solar_widget) }
end
If I uncomment the create(:user) part, I get InvalidFactoryError for the User factory. My guess is because there is nothing in the User factory that states it has any user_widgets. I will experiment a bit with callbacks to see if I can resolve this.
UPDATE 2
I've managed to solve this by adding this to my User factory:
trait :with_widgets do
after(:create) do |user|
user.user_widgets << create(:user_solar_widget, user: user)
end
end
Where user_widgets is a has_many association in the user model.
Then I changed my user_solar_widget factory to:
factory :user_solar_widget, :class => 'UserWidget' do
sequence_number 2
# removed the user line
widget { Widget.find_by_type('SolarWidget') || create(:solar_widget) }
end
I then create a user by calling:
create :user, :with_widgets
Still, it would have been nice if the lint function was a bit more specific about invalid factories..
FactoryGirl.lint is almost useless because of it's non-existent error messages. I recommend instead including the following test:
# Based on https://github.com/thoughtbot/factory_girl/
# wiki/Testing-all-Factories-(with-RSpec)
require 'rails_helper'
RSpec.describe FactoryGirl do
described_class.factories.map(&:name).each do |factory_name|
describe "#{factory_name} factory" do
it 'is valid' do
factory = described_class.build(factory_name)
expect(factory)
.to be_valid, -> { factory.errors.full_messages.join("\n") }
end
end
end
end

avoid factorygirl repeate the number when run second time in cucumber

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

File upload, factory_girl & database_cleaner

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

Rails Custom Validators: Testing options

I'm trying to write up a rails gem that involves (amongst other things) some custom model validators...and I'm wondering how to test validation options.
To give an example, I'd like to write an rspec test for which a blank field returns valid if the allow_nil option is true, and invalid otherwise. The code works fine, but I can't think of an elegant way to test it. The code itself:
Module ActiveModel
module Validations
module ThirstyVals
class ValidatePrime < EachValidator
# Validate prime numbers
def validate_each(record, attr_name, value)
return if options[:allow_nil] && value.strip.length == 0
# Other validation code here
# ...
end
end
end
end
end
I'm currently testing through a dummy project, which is fine, but the only way I can think of to test the :allow_nil option is to write up a new attribute with :allow_nil set, and verify its functionality...which seems both excessive and pretty inelegant. There must be a more graceful way - any ideas appreciated. (Other tests below for posterity)
# ...
before(:each) do
#entry = Entry.new
end
describe "it should accept valid prime numbers" do
['7', '13', '29'].each do |n|
#entry.ticket = n
#entry.valid?('ticket').should be_true
end
end
describe "it should reject non-prime numbers" do
['4', '16', '33'].each do |n|
#entry.ticket = n
#entry.valid?('ticket').should be_false
end
end
have you considered testing the validator in isolation like so:
in validate_prime_spec.rb
require path_to_validator_file
describe ActiveModel::Validations::ThirstyVals::ValidatePrime do
context :validate_each do
it 'should do some stuff' do
subject.validate_each(record, attr_name, value).should #some expectation
end
end
end
then may I suggest that you need not test the allow_nil functionality of Rails validations due to the fact that it is already tested in Rails? (see: activemodel/test/cases/validations/inclusion_validation_test.rb line 44)

Resources