如何在没有数据库的情况下模拟ActiveRecord模型规范中的唯一性验证错误?

时间:2014-04-23 15:32:32

标签: ruby-on-rails activerecord rspec activemodel

我有一个ActiveRecord模型,它具有唯一性验证:

class Profile < ActiveRecord::Base
  # fields: :name, :slug

  before_validation :set_default_slug_from_name

  validates :slug, uniqueness: {case_sensitive: false},
                   unless: -> { |p| p.slug.blank? }

  # ...
end

现在,在编写此模型的规范时,我想模拟唯一性验证错误而不会访问数据库,这样我就可以测试一些取决于这种错误的模型行为:

describe Profile
  before { subject.name = "Bob Greenfield" }

  it "modifies the beginning of the slug if there is a duplicate" do
    # simulate uniqueness conflict (duplicate record) here
    subject.valid?
    expect(subject.slug).to match(/^\w+-bob-greenfield$/)
  end
end

我挖掘了实现UniquenessValidator的{​​{3}}并尝试了以下内容:

allow_any_instance_of(ActiveRecord::Relation).to receive(:exists?).and_return(true)
# ...

但这似乎不起作用。

1 个答案:

答案 0 :(得分:3)

我想你在这里试图有点太深了。虽然阅读rails源并了解其工作原理非常棒,但是模拟Rails内部使用的每个类实例并不是一个好主意。在这种情况下,与其他验证器相反,UniquenessValidator确实依赖于数据库,因此您应该:

一个。允许自己点击数据库。在这种情况下,这不是一个巨大的开销,也可能是实现这一目标的最务实的方法。你甚至使用&#34;重复记录&#34;在您的规范中的评论中,为什么不在那里有记录?在10个案例中的9个案例中,这是正确的方法。毕竟你正在检查一种ActiveRecord :: Base对象。

湾检查独立于rails验证的行为。无论如何应该这样做来测试你喜欢的slug格式。最好在确认之前确保默认slug有效。 这里的缺点是,在这次调用和实际验证之前,很少有机会被其他人占用,但是Rails建立在验证中是一样的,所以我不担心它 - 你最终会出错,接下来的尝试很可能会成功。

it "modifies the beginning of the slug on validation if there is a duplicate" do
    Profile.stub(:slug_exists?).with("bob-greenfield").and_return(true)
    Profile.stub(:slug_exists?).with("1-bob-greenfield").and_return(true)
    Profile.stub(:slug_exists?).with("2-bob-greenfield").and_return(false)

    subject.set_default_slug_from_name
    subject.slug.should == "2-bob-greenfield"
end

并且您已经知道Rails在验证之前调用,因此您不必测试此行为。

此外,最好确保您对数据库有约束,Rails唯一性是不够的:http://robots.thoughtbot.com/the-perils-of-uniqueness-validations