如何让PHPUnit Mock对象根据参数返回不同文件内容?
问题:PHPUnit Mock Redis服务根据Key返回对应JSON文件内容失败
问题背景
- 需要Mock Redis服务的
getAuthData方法,根据传入的key返回本地JSON文件内容,用于测试复杂用户权限 - 当前使用PHPUnit 9,即将升级到PHPUnit 10,需避免使用
at/withConsecutive等废弃方法 - 尝试用
returnCallback实现,但触发类型错误:TypeError: Mock_AuthRedisServiceInterface_4131c2db::getAuthData(): Return value must be of type string, null returned
错误原因
回调函数无法访问外部作用域的$authJson和$userGlobalsJson变量,导致匹配不到key时返回null,违反方法的返回类型约束。
解决方案
通过PHP的use关键字让回调函数捕获外部变量,同时添加默认返回值避免返回null:
修改后的代码:
private function mockAuthRedisService( string $authFile = 'auth_admin', string $userGlobalsFile = 'user_globals_admin' ): MockObject|AuthRedisServiceInterface { $authJson = $this->readfile($authFile); $userGlobalsJson = $this->readfile($userGlobalsFile); $redisService = $this->getMockBuilder(AuthRedisServiceInterface::class) ->disableOriginalConstructor() ->getMock(); $redisService->expects($this->any()) ->method('getAuthData') ->will( $this->returnCallback( // 使用use关键字引入外部变量 function(string $keyName) use ($authJson, $userGlobalsJson) { if (str_contains($keyName, 'authInfo')) { // PHP 8+推荐用str_contains替代strstr return $authJson; } if (str_contains($keyName, 'userGlobals')) { return $userGlobalsJson; } // 添加默认返回值,避免返回null违反类型约束 return json_encode([]); } ) ); return $redisService; } private function readfile(string $name): string { $path = __DIR__ . '/Unit/Security/test_data_files/' . $name . '.json'; // 可选:添加文件存在校验,提前发现缺失的测试数据 if (!file_exists($path)) { throw new RuntimeException("测试数据文件不存在:{$path}"); } return file_get_contents($path); }
关键修改点
- 回调函数通过
use ($authJson, $userGlobalsJson)捕获外部变量,解决作用域访问问题 - 用
str_contains替代strstr(PHP 8+语义更清晰,需兼容PHP 7.x可保留strstr) - 添加默认返回值(如空JSON字符串),确保所有输入都符合方法的返回类型要求
- 可选:在
readfile中增加文件存在校验,提前排查测试数据缺失问题
额外优化建议
如果需要支持更多key与文件的映射,可改用数组配置的方式提升可维护性:
private function mockAuthRedisService(array $keyFileMap = [ 'authInfo' => 'auth_admin', 'userGlobals' => 'user_globals_admin' ]): MockObject|AuthRedisServiceInterface { // 预加载所有需要的JSON内容 $jsonMap = []; foreach ($keyFileMap as $keyKeyword => $fileName) { $jsonMap[$keyKeyword] = $this->readfile($fileName); } $redisService = $this->getMockBuilder(AuthRedisServiceInterface::class) ->disableOriginalConstructor() ->getMock(); $redisService->expects($this->any()) ->method('getAuthData') ->will( $this->returnCallback( function(string $keyName) use ($jsonMap) { foreach ($jsonMap as $keyword => $jsonContent) { if (str_contains($keyName, $keyword)) { return $jsonContent; } } return json_encode([]); } ) ); return $redisService; }
后续新增key映射时,只需修改$keyFileMap参数即可,无需调整回调逻辑。
内容的提问来源于stack exchange,提问作者Calamity Jane
相关产品推荐
相关产品推荐

