Mock中对被其他函数调用的函数设置shouldReceive断言未生效,如何解决?
兄弟,我一眼就看出问题所在了——你对Mock的核心逻辑理解错了!当你创建一个**完整Mock(Full Mock)**的时候,这个对象的所有方法都会被替换成空的Mock实现,包括你写的foo()方法本身!
也就是说,你原类里foo()中判断$value instanceof SomeClass然后调用bar()的逻辑,根本就没执行!因为Mock的foo()是个空架子,不会走原来的代码,自然不会触发bar()的调用,那你的shouldReceive('bar')或者shouldNotReceive('bar')当然永远不会生效啦。
问题根源
你现在的Mock把整个类的方法都覆盖了,原类的业务逻辑完全没跑。Mock的本质是模拟外部依赖的行为,或者验证方法的调用情况,但如果你Mock的是你要测试的类本身,又想保留部分方法的原逻辑,就不能用普通的Full Mock。
正确的做法
1. 使用Partial Mock(部分Mock)
Partial Mock会保留原类的方法实现,只Mock你需要监控或者替换的方法。比如用Mockery的话:
// 创建部分Mock,保留原类的方法逻辑 $mock = Mockery::mock(YourClass::class)->makePartial(); // 根据传入的$suppliedValue设置断言 if ($suppliedValue instanceof SomeClass) { // 预期bar()会被调用一次 $mock->shouldReceive('bar')->once()->with($suppliedValue); } else { // 预期bar()不会被调用 $mock->shouldNotReceive('bar'); } // 执行测试方法 $mock->aFunctionThatCallsFoo($suppliedValue);
这样foo()会执行原有的判断逻辑,当满足条件时会调用bar(),这时Mock的bar()断言就能正确触发了。
2. 直接测试foo()方法(更推荐)
如果你的测试目标是验证foo()在不同参数下是否正确调用bar(),那没必要绕弯子调用aFunctionThatCallsFoo,直接实例化原类,传入不同的参数,用Spy来监控bar()的调用:
// 创建Spy对象,它会记录所有方法调用 $spy = Mockery::spy(YourClass::class); // 直接调用foo() $spy->foo('test-key', []); // 验证bar()没被调用 $spy->shouldNotHaveReceived('bar'); // 再传一个SomeClass实例 $someClass = new SomeClass(); $spy->foo('test-key', $someClass); // 验证bar()被调用了一次,参数正确 $spy->shouldHaveReceived('bar')->once()->with($someClass);
Spy比Partial Mock更适合这种监控内部方法调用的场景,因为它默认保留所有原方法逻辑,只是记录调用情况。
最后再敲个黑板
记住:Full Mock会替换所有方法,原逻辑完全不执行;Partial Mock/Spy会保留原逻辑,只监控或替换指定方法。你之前的问题就是用了Full Mock,导致foo()的内部逻辑根本没跑,自然触发不了bar()的断言。
内容的提问来源于stack exchange,提问作者Edwin

