工厂女孩创建绕过我的模型验证


77

我正在使用Factory Girl在模型/单元测试中为组创建两个实例。我正在测试该模型以检查对.current的调用是否根据如下所述的expiry属性仅返回“当前”组...

  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创建时,是否有办法强制创建组或解决验证问题?

Answers:


91

这不是FactoryGirl特有的,但是通过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

请参阅下面的Jason Denney答案,以获得更好的解决方案。
David Hempy

1
从1.9.1开始,您可以做g.tap { |g| g.save(validate: false) }
yefrem '19

58

我更喜欢https://github.com/thoughtbot/factory_girl/issues/578的解决方案。

工厂内部:

to_create {|instance| instance.save(validate: false) }

编辑:

如所引用线程中所述,以及其他人的评论/解决方案,您可能希望将其包装在trait块中,以避免测试中其他地方的混乱/问题。例如,当您测试验证时。


5
这是比公认的解决方案更为优雅的解决方案。
凯尔·赫罗尼穆斯

5
请记住,如果您为通用工厂执行此操作,则每次在该工厂上创建时都会跳过验证。最好只在子工厂(或特征)上使用此技术。
tgf

您几乎肯定会希望将其作为特征。请参阅下面的Tim Scott的答案。
David Hempy

39

在工厂默认情况下跳过验证是一个坏主意。一些头发会被拉出来。

我认为最好的方法是:

trait :skip_validate do
  to_create {|instance| instance.save(validate: false)}
end

然后在您的测试中:

create(:group, :skip_validate, expiry: Time.now + 1.week)

1
这是解决此问题的最佳方法!
hatenine

有没有办法将其应用于所有工厂?
adaam

在预先存在的代码库中找到这些小片段总是令人惊奇的。您知道现任或前任同事曾经找到过此确切答案。
迪伦·皮尔斯



6

最好不要跳过对该模型的所有验证。

创建spec/factories/traits.rb文件。

FactoryBot.define do
  trait :skip_validate do
    to_create { |instance| instance.save(validate: false) }
  end
end

修复规格

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

1

根据您的方案,您可以将验证更改为仅在更新时进行。例::validates :expire_date, :presence => true, :on => [:update ]


1

您的工厂默认情况下应创建有效的对象。我发现瞬态属性可用于添加条件逻辑,如下所示:

transient do
  skip_validations false
end

before :create do |instance, evaluator|
  instance.save(validate: false) if evaluator.skip_validations
end

在您的测试中:

create(:group, skip_validations: true)

0

或者,您可以同时使用它们FactoryBotTimecop类似的东西:

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) }

编辑:创建后请勿更新此事件,否则验证将失败。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.