Factory-girl create that bypasses my model validation
-
27-10-2019 - |
سؤال
I am using Factory Girl to create two instances in my model/unit test for a Group. I am testing the model to check that a call to .current returns only the 'current' groups according to the expiry attribute as per below...
describe ".current" do
let!(:current_group) { FactoryGirl.create(:group, :expiry => Time.now + 1.week) }
let!(:expired_group) { FactoryGirl.create(:group, :expiry => Time.now - 3.days) }
specify { Group.current.should == [current_group] }
end
My problem is that I've got validation in the model that checks a new group's expiry is after today's date. This raises the validation failure below.
1) Group.current
Failure/Error: let!(:expired_group) { FactoryGirl.create(:group, :expiry => Time.now - 3.days) }
ActiveRecord::RecordInvalid:
Validation failed: Expiry is before todays date
Is there a way to forcefully create the Group or get around the validation when creating using Factory Girl?
المحلول
This isn't very specific to FactoryGirl, but you can always bypass validations when saving models via save(:validate => false)
:
describe ".current" do
let!(:current_group) { FactoryGirl.create(:group) }
let!(:old_group) {
g = FactoryGirl.build(:group, :expiry => Time.now - 3.days)
g.save(:validate => false)
g
}
specify { Group.current.should == [current_group] }
end
نصائح أخرى
I prefer this solution from https://github.com/thoughtbot/factory_girl/issues/578.
Inside the factory:
to_create {|instance| instance.save(validate: false) }
EDIT:
As mentioned in the referenced thread, and by other's comments/solutions, you'll likely want to wrap this in a trait block to avoid confusion/issues elsewhere in your tests; for example, when you're testing your validations.
It's a bad idea to skip validations by default in factory. Some hair will be pulled out finding that.
The nicest way, I think:
trait :skip_validate do
to_create {|instance| instance.save(validate: false)}
end
Then in your test:
create(:group, :skip_validate, expiry: Time.now + 1.week)
For this specific date-baesd validation case, you could also use the timecop gem to temporarily alter time to simulate the old record being created in the past.
foo = build(:foo).tap{ |u| u.save(validate: false) }
Your factories should create valid objects by default. I found that transient attributes can be used to add conditional logic like this:
transient do
skip_validations false
end
before :create do |instance, evaluator|
instance.save(validate: false) if evaluator.skip_validations
end
In your test:
create(:group, skip_validations: true)
It is not best to skip all validation of that model.
create spec/factories/traits.rb
file.
FactoryBot.define do
trait :skip_validate do
to_create { |instance| instance.save(validate: false) }
end
end
fix spec
describe ".current" do
let!(:current_group) { FactoryGirl.create(:group, :skip_validate, :expiry => Time.now + 1.week) }
let!(:expired_group) { FactoryGirl.create(:group, :skip_validate, :expiry => Time.now - 3.days) }
specify { Group.current.should == [current_group] }
end
Depending on your scenario you could change validation to happen only on update. Example: :validates :expire_date, :presence => true, :on => [:update ]