RSpec测试未通过:已确认方法调用却无法验证ActiveRecord事务
问题描述
我写了一个UserRewards类,其self.grant方法内部调用了ActiveRecord::Base.transaction。但对应的RSpec测试在执行grant后,验证ActiveRecord::Base是否收到:transaction调用时始终失败——明明调试已经确认这个方法确实执行了,想知道是不是需要对ActiveRecord::Base的方法做Stub处理。
类代码
class UserRewards def self.grant(reward_id:, submission_id:, user_id:) debugger ActiveRecord::Base.transaction do puts 'ActiveRecord::Base.transaction' end end end
测试代码
require 'rails_helper' RSpec.describe UserRewards do describe '.grant' do it 'grants a reward to a user' do user = build_stubbed(:user) submission = build_stubbed(:submission) reward = build_stubbed(:reward) UserRewards.grant( reward_id: reward.id, submission_id: submission.id, user_id: user.id, ) expect(ActiveRecord::Base).to receive(:transaction) end end end
测试失败信息
1) UserRewards.grant grants a reward to a user Failure/Error: expect(ActiveRecord::Base).to receive(:transaction) (ActiveRecord::Base (class)).transaction(*(any args)) expected: 1 time with any arguments received: 0 times with any arguments
调试确认方法运行
(rdbg) n # next command [1, 10] in ~/workspace/my-app/lib/services/user_rewards.rb 1| class UserRewards 2| def self.grant(reward_id:, submission_id:, user_id:) 3| debugger 4| ActiveRecord::Base.transaction do => 5| puts 'ActiveRecord::Base.transaction' 6| end 7| end 8| 9| def self.revoke(reward_id:, submission_id:, user_id:) =>#0 block in grant at ~/workspace/my-app/lib/services/user_rewards.rb:6 #1 block in within_new_transaction at ~/.rbenv/versions/3.0.3/lib/ruby/gems/3.0.0/gems/activerecord-7.0.1/lib/active_record/connection_adapters/abstract/transaction.rb:319 # and 74 frames (use `bt' command for all frames)
解决方法
核心问题是RSpec消息预期的设置时机错误:你先执行了UserRewards.grant(此时事务已经跑完了),才设置expect(ActiveRecord::Base).to receive(:transaction),RSpec只会监听设置预期之后的方法调用,自然捕获不到之前的执行。
1. 调整预期与代码执行顺序
把预期设置放在调用grant方法之前,就能正确捕获到事务调用:
require 'rails_helper' RSpec.describe UserRewards do describe '.grant' do it 'grants a reward to a user' do user = build_stubbed(:user) submission = build_stubbed(:submission) reward = build_stubbed(:reward) # 先设置预期 expect(ActiveRecord::Base).to receive(:transaction) # 再执行方法 UserRewards.grant( reward_id: reward.id, submission_id: submission.id, user_id: user.id, ) end end end
2. 按需Stub(保留原方法行为)
如果不想让事务真的执行(比如避免数据库操作),或者想验证调用同时保留原逻辑,可以用and_call_original:
# 验证调用,同时执行原事务逻辑 expect(ActiveRecord::Base).to receive(:transaction).and_call_original # 或者只验证调用,不执行原逻辑(Stub掉) expect(ActiveRecord::Base).to receive(:transaction).and_return(true)
为什么之前的调试能看到执行但测试失败?
调试时确实跑了transaction方法,但测试里的预期是在方法执行完毕后才设置的——RSpec的expect(...).to receive(...)是前置断言,必须在目标代码执行前声明,才能监听后续的方法调用。
内容的提问来源于stack exchange,提问作者Doug
相关产品推荐
相关产品推荐

