Rspec测试after_create_commit回调未触发问题求助
首先,咱们得先理清两个核心问题:你的测试没有正确触发after_create_commit回调的条件,同时你的模型逻辑本身存在验证和回调顺序的冲突,导致用户根本无法正常创建。
一、修正模型逻辑(最关键的前提)
你当前的代码里,validates :roles, presence: true, on: :save会在用户保存时校验角色是否存在,但assign_role是在after_create_commit(也就是保存成功、事务提交后)才会给用户添加角色。这就导致了一个死循环:保存时因为角色为空触发验证失败,用户创建不了,回调自然也不会执行。
你需要调整回调时机,确保在验证之前就给用户分配默认角色,比如改成before_validation:
class User < ApplicationRecord has_and_belongs_to_many :roles validates :roles, presence: true before_validation :assign_role, on: :create private def assign_role self.roles << Role.client if roles.empty? end end
这样在创建用户时,会先执行assign_role添加默认角色,再走验证逻辑,就能正常通过了。如果坚持要用after_create_commit,那你得去掉角色的存在性验证(或者把验证时机改成仅更新时触发),否则用户创建会一直失败。
二、修正Rspec测试,触发after_create_commit回调
after_create_commit是复合回调:它要求先完成create事件(用户成功保存到数据库),再完成事务提交,才会触发对应的方法。你之前用build(:user)只是在内存中创建对象,既没有执行create回调,也没有事务提交的过程,所以回调肯定不会触发。
方案1:手动触发create+commit回调
适合不想修改测试环境配置的场景,先保存用户(执行create回调),再手动触发commit阶段的回调:
describe '#assign_role' do context 'when creating a new user' do it 'is triggered' do user = build(:user) expect(user).to receive(:assign_role) user.save! # 执行create回调,注册after_commit回调 user.run_callbacks(:commit) # 触发commit阶段的回调 end end end
方案2:禁用事务测试,让事务真正提交
如果你希望测试更贴近生产环境的真实行为,可以禁用Rspec默认的事务测试(默认事务会在测试结束后回滚,不会真正提交),配合DatabaseCleaner管理测试数据:
- 在
rails_helper.rb中修改配置:
RSpec.configure do |config| # 禁用事务测试 config.use_transactional_fixtures = false # 配置DatabaseCleaner require 'database_cleaner' config.before(:suite) do DatabaseCleaner.strategy = :truncation DatabaseCleaner.clean_with(:truncation) end config.before(:each) do DatabaseCleaner.start end config.after(:each) do DatabaseCleaner.clean end end
- 修改测试代码,直接创建用户(事务提交后自动触发
after_create_commit):
describe '#assign_role' do context 'when creating a new user' do it 'is triggered' do user = build(:user) expect(user).to receive(:assign_role) user.create! # 事务提交后自动触发回调 end end end
方案3:手动触发after_commit内部回调
如果你不想实际提交事务,可以调用Rails内部的私有方法run_after_commit_callbacks(注意:私有方法可能在Rails版本更新中变化,需谨慎使用):
describe '#assign_role' do context 'when creating a new user' do it 'is triggered' do user = build(:user) expect(user).to receive(:assign_role) user.save! user.send(:run_after_commit_callbacks) # 触发after_commit回调逻辑 end end end
内容的提问来源于stack exchange,提问作者chell

