如何在Ruby的Mocha单元测试中使用Spy而不覆盖方法实现?
当然可行!用Mocha实现不覆盖原逻辑的Spy
你遇到的问题其实是Mocha默认行为的小坑——expects确实会替换目标方法的实现(默认返回nil),但它提供了专门的API来保留原方法逻辑,同时完成调用验证,完全满足你的需求。
核心方案:使用calls_original方法
Mocha的calls_original方法就是为这个场景设计的:它会让你监听的方法在被调用时,执行原有的实现逻辑,而不是被Mocha的模拟逻辑替换。结合expects就能同时完成「验证调用」和「保留原功能」两个目标。
举个实际的代码例子:
假设你有被测类OrderProcessor,它会调用InventoryChecker的check_stock方法,你要验证这个调用发生,同时不改变check_stock的原有返回值:
# 被测代码 class OrderProcessor def process(order) inventory_checker = InventoryChecker.new if inventory_checker.check_stock(order[:item_id]) > 0 "processed" else "out of stock" end end end class InventoryChecker def check_stock(item_id) # 原有的库存查询逻辑,比如查数据库 10 # 模拟返回值 end end
对应的测试代码:
require 'minitest/autorun' require 'mocha/minitest' class OrderProcessorTest < Minitest::Test def test_process_calls_check_stock # 实例化依赖类 inventory_checker = InventoryChecker.new # 关键:用expects监听方法,同时指定calls_original保留原逻辑 inventory_checker.expects(:check_stock).with(123).calls_original # 执行被测方法 result = OrderProcessor.new.process(item_id: 123) # 验证原方法的返回值依然生效 assert_equal "processed", result # Mocha会自动验证check_stock被调用了一次,无需额外断言 end end
更灵活的场景:用Stub+Spy记录调用
如果你只是想记录调用情况,而不是强制要求方法必须被调用(比如某些分支场景),可以用stubs配合Mocha的Spy对象:
def test_process_may_call_check_stock inventory_checker = InventoryChecker.new # 创建一个stub,保留原逻辑,同时返回spy对象 stock_check_spy = inventory_checker.stubs(:check_stock).calls_original OrderProcessor.new.process(item_id: 456) # 手动检查调用情况 assert stock_check_spy.called? assert stock_check_spy.called_with?(456) assert_equal 1, stock_check_spy.call_count end
关键知识点回顾
expects(:method):强制验证方法被调用(次数、参数),默认替换实现.calls_original:让监听的方法执行原逻辑,不替换实现stubs(:method).calls_original:保留原逻辑,同时返回可查询的Spy对象,适合非强制验证的场景
这样就能完美实现你要的「只验证调用,不覆盖原方法」的Spy功能啦!
内容的提问来源于stack exchange,提问作者Jon Schneider
相关产品推荐
相关产品推荐

