Factory-girl创建的模型绕过了我的验证。

99

我正在使用Factory Girl在我的模型/单元测试中创建两个Group实例。我正在测试该模型以检查对.current的调用是否只返回根据过期属性为“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
有没有一种强制创建 Group 或绕过使用 Factory Girl 创建时的验证的方法?
12个回答

110

这并不是很针对FactoryGirl,但你总是可以通过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

请参考Jason Denney的下面的答案,那里有更好的解决方案。 - David Hempy
2
自1.9.1版本以来,您可以执行g.tap { |g| g.save(validate: false) } - yefrem

87

7
这个解决方案比被接受的那个更加优雅。 - Kyle Heironimus
6
请记住,如果您为通用工厂执行此操作,则每次在该工厂上执行创建操作时都会跳过验证步骤。最好仅在子工厂(或 Trait 中)使用此技术。 - tgf
你几乎肯定会想把这个放在一个特质中。请参见下面 Tim Scott 的答案。 - David Hempy

58

默认情况下跳过工厂中的验证是不好的想法。这会导致一些问题。

我认为最好的方式:

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

然后在你的测试中:

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

有没有一种方法可以将此应用于所有工厂? - adaam

19
foo = build(:foo).tap { |u| u.save(validate: false) }

1
很适合只需要在单个规范上使用的一次性情况。 - UsAndRufus
与其他笨重的答案相比,这是一个简单而优雅的单行代码。 - anothermh

8

针对这个特定的日期验证案例,您还可以使用timecop宝石来暂时更改时间,模拟旧记录在过去创建的场景。


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

2

你的工厂应该默认创建有效的对象。我发现临时属性可以用来添加像这样的条件逻辑:

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)

1

我在我的模型中添加了一个attr_accessor来跳过日期检查:

attr_accessor :skip_date_check

然后,在验证过程中,如果有指定,它将会跳过:

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

然后在我的工厂中,我添加了一个选项来创建旧事件:

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

    factory :old_event do
      skip_date_check { true }
    end
  end

结束


0

另一种解决方案是创建自定义策略:

# spec/support/factory_bot.rb

class SkipValidationStrategy
  def initialize
    @strategy = FactoryBot.strategy_by_name(:build).new
  end

  delegate :association, to: :@strategy

  def result(evaluation)
    @strategy.result(evaluation).tap do |instance|
      instance.save!(validate: false)

      raise "Instance of #{instance.class} is expected to be invalid" if instance.valid?
    end
  end
end

FactoryBot.register_strategy(:create_invalid, SkipValidationStrategy)

然后在你的规范中,你可以使用

let!(:current_group) { create_invalid(:group, expiry: 1.week.since) }

请看这里: https://github.com/thoughtbot/factory_bot/blob/main/GETTING_STARTED.md#custom-strategies


0
根据您的情况,您可以将验证更改为仅在更新时发生。例如::validates :expire_date, :presence => true, :on => [:update ]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接