如何解决针对模型的Ruby on Rails RSpec测试仅允许数据库中的一条记录
我正在尝试为我的模型建立RSpec测试规范:徽标,该徽标将确保只能将单个记录保存到数据库中。当我第二次调用.build
方法来构建徽标时,由于FactoryBot能够构建徽标,因此测试失败。
但是,如果我在FactoryBot中的第二个徽标条目中使用.create
方法,则会收到测试错误,因为我的模型根据模型的{{1} }方法。
如何使用RSpec和FactoryBot进行这项工作?
这是我尝试的代码,但未成功:
:only_one_row
# app/models/logo.rb
class Logo < ApplicationRecord
before_create :only_one_row
private
def only_one_row
raise "You can only have one logo file for this website application" if Logo.count > 0
end
end
# spec/factories/logos.rb
FactoryBot.define do
factory :logo do
image { File.open(File.join(Rails.root,'spec','fixtures','example_image.jpg')) }
end
end
解决方法
您在此处的验证是作为一个钩子而不是验证来实现的,这就是be_valid
调用永远不会失败的原因。我想指出的是,从逻辑角度来看,这里没有真正的问题-硬异常,因为在这种情况下进行健全性检查似乎是可以接受的,因为应用程序不应该尝试这样做。您甚至可以重写测试以明确地对其进行测试:
it 'can not have more than one record' do
# Ensure there are no logo records in the database before this test is run.
Logo.destroy_all
example_logo_one = FactoryBot.create(:logo)
expect { FactoryBot.create(:logo) }.to raise_error(RuntimeError)
end
但是,如果应用程序有可能尝试它并且您想要更好的用户体验,则可以将其构建为验证。棘手的部分是,对于未保存的徽标(我们需要确保没有其他已保存的徽标,期限)与现有徽标(我们只需要验证我们是唯一的)之间的验证看起来有所不同。我们可以通过确保没有不属于该徽标的徽标来使它成为一项检查:
class Logo < ApplicationRecord
validate do |logo|
if Logo.first && Logo.first != logo
logo.errors.add(:base,"You can only have one logo file for this website application")
end
end
end
此验证将允许保存第一个徽标,但应立即通过您的原始规格,知道第二个徽标无效。
,当我第二次调用.build方法来构建徽标时,由于FactoryBot能够构建徽标,因此测试失败。
是的,build
不保存对象。
但是,如果我在FactoryBot中的第二个徽标条目中使用.create方法,则会收到测试错误,因为我的模型根据我的模型的:only_one_row方法的方法产生了指示错误。
使用expect
块和raise_error
matcher捕获异常。
context 'with one Logo already saved' do
let!(:logo) { create(:logo) }
it 'will not allow another' do
expect {
create(:logo)
}.to raise_error("You can only have one logo file for this website application")
end
end
请注意,这必须将异常消息硬编码到测试中。如果消息更改,则测试将失败。您可以测试RuntimeError,但是任何 RuntimeError都会通过测试。
为避免这种情况,请创建RuntimeError的子类,提出该子类,然后测试该特定异常。
class Logo < ApplicationRecord
...
def only_one_row
raise OnlyOneError if Logo.count > 0
end
class OnlyOneError < RuntimeError
MESSAGE = "You can only have one logo file for this website application".freeze
def initialize(msg = MESSAGE)
super
end
end
end
然后您可以测试该异常。
expect {
create(:logo)
}.to raise_error(Logo::OnlyOneError)
请注意,如果您的测试和测试数据库的设置正确,则Logo.destroy_all
是不必要的。每个测试示例都应从一个干净的空数据库开始。
这里有两件事:
如果您的整个应用程序根本只允许一个徽标(而不是每个公司,每个用户或任何东西一个徽标),那么我认为没有理由将其放入数据库中。取而代之的是,只需将其放入文件系统并完成操作即可。
如果尽管有我之前的评论,如果有充分的理由将其保存在数据库中,并且您真的想确保只有一个徽标,那么我强烈建议在数据库级别设置此约束。想到的两种方法是撤销相关表的INSERT
特权,或者定义一个触发器,该触发器阻止INSERT
查询(如果该表已经有记录)。
这种方法非常重要,因为它很容易被遗忘:1)验证可以有意或无意地被绕开(save(validate: false)
,update_column
等),以及2)除您的应用程序外,其他客户端也可以访问数据库(例如另一个应用程序,数据库自己的控制台工具等)。如果要确保数据完整性,则必须在数据库级别执行此类基本操作。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。