工厂女孩的创造,绕过了我的模型验证

我使用 Factory Girl 在我的模型/单元测试中为一个组创建两个实例。我正在测试模型,以检查一个调用。Current 根据到期属性只返回“当前”组,如下所示..。

  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

我的问题是,我在检查一个新组的有效期是在今天的日期之后的模型中得到了验证。这将引发下面的验证失败。

  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

在使用 Factory Girl 创建时,是否有强制创建组或绕过验证的方法?

41589 次浏览

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) do
g = FactoryGirl.build(:group, expiry: Time.now - 3.days)
g.save(validate: false)
g
end
      

specify { Group.current.should == [current_group] }
end

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.

Depending on your scenario you could change validation to happen only on update. Example: :validates :expire_date, :presence => true, :on => [:update ]

I prefer this solution from https://github.com/thoughtbot/factory_girl/issues/578.

Inside the factory:

trait :without_validations do
to_create { |instance| instance.save(validate: false) }
end
foo = build(:foo).tap { |u| u.save(validate: false) }

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)

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

Or you can use both FactoryBot and Timecop with something like:

trait :expired do
transient do
travel_backward_to { 2.days.ago }
end
before(:create) do |_instance, evaluator|
Timecop.travel(evaluator.travel_backward_to)
end
after(:create) do
Timecop.return
end
end


let!(:expired_group) { FactoryGirl.create(:group, :expired, travel_backward_to: 5.days.ago, expiry: Time.now - 3.days) }

Edit: Do not update this event after creation or validations will fail.

I added an attr_accessor to my model to skip the date check:

attr_accessor :skip_date_check

Then, in the validation, it will skip if so specified:

def check_date_range
unless skip_date_check
... perform check ...
end
end

Then in my factory, I added an option to create an old event:

FactoryBot.define do
factory :event do
[...whatever...]


factory :old_event do
skip_date_check { true }
end
end

end

Adding a FactoryBot trait to skip validations optionally, as some contending answers suggest, makes sense. An alternative is to stub the model for the specific test case(s) where you don't want validation. This adds a couple of lines of code but is arguably more discoverable. You also have more control over which methods to avoid calling.

Modern RSpec example:

before(:each) do
allow_any_instance_of(MyModel).
to receive(:my_validation_method).
and_return(nil)
end