Rspec测试含两个指向User关联的Friend模型报nil错误如何解决
问题原因
- 自定义校验没有做非空判断:
uniqueness_of_users_associations和cant_be_friend_with_yourself两个校验方法执行时,没有判断user1、user2是否存在就直接调用了user1.friends,只要user1为nil就会抛出你遇到的错误。 shoulda-matchers的belong_to关联测试默认不会使用你的FactoryBot工厂生成实例:它会直接初始化一个无任何属性的空Friend实例执行校验逻辑,此时user1、user2都是nil,自然触发报错,这个问题和FactoryBot的定义无关。
解决方案
你可以任选一种方案解决问题:
方案1:给自定义校验增加执行前置条件
仅当user1和user2都存在时才执行自定义校验,适配Rails默认的belong_to必填校验逻辑,修改Friend.rb的校验声明即可:
# 只有user1和user2都赋值后才执行自定义校验 validate :uniqueness_of_users_associations, :cant_be_friend_with_yourself, if: -> { user1.present? && user2.present? }
方案2:给关联测试指定带属性的测试主体
修改friend_spec.rb,显式指定测试用的实例是通过你的FactoryBot工厂生成的,确保user1、user2有值:
RSpec.describe Friend, type: :model do describe 'relationships' do # 用工厂构建实例,user1和user2已自动赋值 subject { build(:friend) } it { is_expected.to belong_to(:user1).class_name('User') } it { is_expected.to belong_to(:user2).class_name('User') } end end
额外优化建议
你当前的唯一性校验是先查出所有关联再合并数组判断是否为空,会有N+1查询问题,而且数据量大时性能很差,建议直接用数据库层面的存在性查询:
def uniqueness_of_users_associations exists = Friend.exists?( "(user1_id = :u1 AND user2_id = :u2) OR (user1_id = :u2 AND user2_id = :u1)", u1: user1.id, u2: user2.id ) errors.add(:friend, 'He is already your friend') if exists end
同时建议给friends表增加(user1_id, user2_id)的联合唯一索引,避免并发请求时出现脏数据。
内容的提问来源于stack exchange,提问作者N0ne
相关产品推荐
相关产品推荐

