PHP Mock测试报方法预期调用1次实际0次异常排查
问题现象
测试执行时抛出错误:Method should be called exactly 1 times but called 0 times
测试代码如下:
$mock = $this->mock(GetAll::class); $mock->shouldReceive('setCustomersQueryParametersDto') ->once() ->with($customerDto) ->andReturnSelf(); $mock->shouldReceive('execute')->once()->andReturn(collect([])); $this->actingAs($user)->json('GET', $this->generateUrl());
测试逻辑预期:GetAll类调用execute方法前,必须先调用setter方法setCustomersQueryParametersDto传入DTO对象。Mock配置已按调用顺序先定义setter规则:预期调用1次、入参为$customerDto、返回Mock对象自身;后续定义execute方法规则:预期调用1次、返回空集合。最后模拟登录用户发起GET请求访问接口,预期测试正常通过,但实际运行时控制器内execute方法返回结果不符合预期。
控制器内对应业务代码如下:
$this->getAllAction->setCustomersQueryParametersDto($customersRequest->model())->execute();
问题原因
核心问题出在两个地方:
- 参数严格匹配不通过:Mockery(Laravel测试Mock功能的底层库)默认对
with()传入的参数做对象引用级别的严格校验,要求传入的参数必须和测试代码中提前创建的$customerDto是同一个实例。但控制器中实际传入的是请求运行时通过$customersRequest->model()动态生成的新DTO实例,哪怕两个实例属性完全一致,因为不是同一个对象引用,会被判定为参数不匹配,导致setter方法的Mock规则完全未命中。 - 规则未命中后,setter方法不会按预期返回Mock实例自身,后续链式调用的
execute方法自然也无法命中提前配置的Mock规则,最终触发「方法预期调用1次实际调用0次」的报错。
修复方案
按以下步骤调整测试代码即可:
- 替换参数匹配规则,放弃对象引用的严格校验,改为校验传入DTO的属性值是否符合预期
- 可选添加
ordered()标记,明确两个方法的调用先后顺序,避免链式调用规则承接异常
修复后的测试代码示例:
$mock = $this->mock(GetAll::class); $mock->shouldReceive('setCustomersQueryParametersDto') ->once() // 改为值匹配,只要传入的DTO属性和预期一致即判定通过,不要求是同一个实例 ->with(\Mockery::on(function($actualDto) use ($customerDto) { // 以下三种值比对方式三选一即可,按需选择 // 1. 如果DTO自带equals方法:return $actualDto->equals($customerDto); // 2. 如果DTO可以转数组:return $actualDto->toArray() === $customerDto->toArray(); // 3. 通用PHP对象值比对:return $actualDto == $customerDto; })) ->andReturnSelf() ->ordered(); // 标记调用顺序:setter必须先执行 $mock->shouldReceive('execute') ->once() ->andReturn(collect([])) ->ordered(); // 标记调用顺序:execute在setter之后执行 $this->actingAs($user)->json('GET', $this->generateUrl());
内容的提问来源于stack exchange,提问作者Milan Milinkovic
相关产品推荐
相关产品推荐

