PHPUnit测试:如何验证Baz类biz方法调用传入的Foo实例的bar方法
这类测试完全可以正常实现,你当前的测试写法存在两个关键的逻辑错误
核心问题定位
你之所以测试不生效,本质是把「测试目标的真实业务逻辑」直接替换成了空实现:
- 你构建
$mockBaz的时候,把biz()方法加到了setMethods/onlyMethods的列表里 - 这个配置的作用是:把列表里的方法全部替换成无逻辑的mock空方法,原来写在
Baz->biz()里的$foo->bar()代码根本不会被执行,自然也就触发不了对$mockFoo->bar()的调用验证
这里要特别明确PHPUnit Mock的工作规则:
只有被列在onlyMethods(旧版是setMethods)里的方法,才会被替换成测试替身的实现;没有列进去的方法,会完完全全保留类里原本写好的真实逻辑。你要测的就是biz()的真实逻辑,当然不能把它列在替换列表里。
正确的测试写法
final class BazTest extends TestCase { public function testBazzerCallsFooInstancesBarMethod() { // 1. 构建依赖对象Foo的mock,只需要替换要验证的bar()方法 $mockFoo = $this->getMockBuilder(Foo::class) ->onlyMethods(['bar']) ->getMock(); // 明确指定:bar()方法必须被恰好调用1次 $mockFoo->expects($this->once()) ->method('bar'); // 2. 构建Baz的可实例化测试替身 // 因为Baz是抽象类没法直接new,所以用mockBuilder生成子类时, // 只需要把还没实现的抽象方法buz()列进去替换成空实现 // 绝对不要把已经写好实现的biz()列在替换列表里 $mockBaz = $this->getMockBuilder(Baz::class) ->onlyMethods(['buz']) ->getMock(); // 3. 直接调用真实的biz()方法,传入mock的Foo对象 // 此时biz()会执行原逻辑里的$foo->bar(),PHPUnit的断言会自动校验调用次数是否符合预期 $mockBaz->biz($mockFoo); } }
额外的踩坑提醒
- 不需要手动给抽象类的所有抽象方法列mock清单,PHPUnit的mockBuilder会自动为所有没有具体实现的抽象方法生成空的可实例化实现,你只需要按需列出来你想要自定义行为的方法就行
- 测试的核心逻辑永远是:保留被测方法的真实逻辑,只把它依赖的外部协作者对象mock成可控的测试替身,这样才能验证被测方法本身的行为是否符合预期,而不是测了个没有逻辑的空壳
内容的提问来源于stack exchange,提问作者Sevi Foreman
相关产品推荐
相关产品推荐

