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

无需额外Gem,如何用RSpec测试报错重试逻辑?

问题分析与解决

代码与测试重现

业务代码

def make_error
  raise StandardError, 'err'
end

class Bob
  def bar(thing)
    puts thing
  end
end

def foo
  ['a', 'b'].each do |x|
    retry_counter = 5
    begin
      bar(x)
      make_error
    rescue StandardError => e
      if retry_counter.positive?
        retry_counter -= 1
        retry
      else
        raise e
      end
    end
  end
end

RSpec测试代码

it 'adds and retries the emails to the digest store' do
  expect(Bob).to receive(:bar).exactly(6).times.with('a')
  expect{ foo }.to raise_error(StandardError)
end

核心问题诊断

  1. 方法调用不匹配:测试期望Bob类的bar类方法被调用,但业务代码里的bar(x)是在顶级作用域调用未定义的方法,实际运行会抛出NoMethodError,而非预期的StandardError。即便rescue能捕获该错误,测试期望与实际调用对象的不匹配也会导致断言逻辑混乱。

  2. RSpec桩行为干扰:默认情况下,expect(Bob).to receive(:bar)会为Bob类创建桩方法,但业务代码根本没调用这个桩方法,导致:

    • 保留第一个断言时,代码因bar(x)未定义抛出错误,重试逻辑虽执行,但RSpec最终因期望未满足失败,且错误类型与测试预期不符。
    • 移除第一个断言时,代码执行到make_error抛出StandardError,断言成功,但无法验证重试次数。

解决方案

步骤1:修正方法调用与定义

将Bob的bar改为类方法,并在foo中明确调用:

class Bob
  def self.bar(thing) # 改为类方法
    puts thing
  end
end

def foo
  ['a', 'b'].each do |x|
    retry_counter = 5
    begin
      Bob.bar(x) # 明确调用类方法
      make_error
    rescue StandardError => e
      if retry_counter.positive?
        retry_counter -= 1
        retry
      else
        raise e
      end
    end
  end
end

步骤2:调整RSpec断言逻辑

使用and_call_original确保桩方法执行原逻辑,同时保留断言验证:

it 'adds and retries the emails to the digest store' do
  expect(Bob).to receive(:bar).exactly(6).times.with('a').and_call_original
  expect { foo }.to raise_error(StandardError)
end

为什么这样能解决问题?

  • 修正后的Bob.bar是类方法,与测试期望的调用对象一致,确保重试逻辑中每次调用都能触发断言计数。
  • and_call_original让RSpec在验证调用次数的同时,执行原方法的puts逻辑,不影响后续make_error的错误抛出。
  • 重试逻辑中retry_counter的作用域正确,会重试5次,总共调用6次Bob.bar('a'),随后抛出StandardError,两个断言均可通过。

内容的提问来源于stack exchange,提问作者mangocaptain

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 06:33:15