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

Ruby on rails 在没有数据库的情况下,如何在ActiveRecord模型规范中模拟唯一性验证错误?,ruby-on-rails,activerecord,rspec,activemodel,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?

我有一个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
我深入研究了实现
uniquenessvalidater

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

但这似乎不起作用。

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

a。允许自己点击数据库。在这种情况下,这不是一个巨大的开销,而且可能是实现这一点的最实际的方法。你甚至在你的规范中的注释中使用了“复制记录”,那么为什么不在那里有一个记录呢?在10个案例中,有9个是正确的方法。毕竟,您正在检查一种ActiveRecord::Base对象

b。检查独立于rails验证的行为。无论如何都应该这样做,以测试您首选的slug格式。最好在验证之前确保默认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的唯一性是不够的:

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