You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 07:05:24