如何使用getMockBuilder()为Mezzio自定义服务实现PHP UnitTest?
问题梳理
你现在的测试代码存在核心逻辑错误,且生产代码的写法无法直接mock依赖:
- 你把需要测试的核心方法
AddHandler::process加入了onlyMethods列表,相当于PHPUnit用假的mock实现替换了真实业务逻辑,测试能通过但完全没有验证你写的代码,属于无效测试。 AddHandler的process方法里直接new CrmApiService()和new CustomHydrator(),紧耦合的写法没有预留mock入口,正常手段无法替换这两个类的实现。
方案1:优化生产代码依赖注入(推荐)
这是最符合开发规范的解法,把依赖通过构造函数注入,方便测试替换:
第一步 修改AddHandler代码
// 新增两个私有属性 private CrmApiService $crmApiService; private CustomHydrator $customHydrator; // 修改构造函数 public function __construct( NoteForm $noteForm, TemplateRendererInterface $renderer, CrmApiService $crmApiService, CustomHydrator $customHydrator ) { $this->noteForm = $noteForm; $this->renderer = $renderer; $this->crmApiService = $crmApiService; $this->customHydrator = $customHydrator; } // process方法里替换原来的new逻辑 // 原来的 $this->usersJson = (new CrmApiService())->getUsers(); 改成: $this->usersJson = $this->crmApiService->getUsers(); // 原来的 $hydratedUsers = (new CustomHydrator())->getHydrated($this->usersJson); 改成: $hydratedUsers = $this->customHydrator->getHydrated($this->usersJson); // 还有后面new CrmApiService()->createNote()也改成用$this->crmApiService调用
第二步 重写测试代码
public function testRendersAddFormProperly() { // 模拟CrmApiService返回的用户json $mockUsersJson = '[{"personReference":"1","givenName":"张","additionalName":"","familyName":"三"}]'; $mockHydratedUsers = json_decode($mockUsersJson, true); // mock两个依赖服务 $crmApiService = $this->prophesize(CrmApiService::class); $crmApiService->getUsers()->willReturn($mockUsersJson); $customHydrator = $this->prophesize(CustomHydrator::class); $customHydrator->getHydrated($mockUsersJson)->willReturn($mockHydratedUsers); // mock模板渲染 $this->renderer ->render("note::edit", Argument::type("array")) ->willReturn('表单html'); // 模拟请求 $serverRequest = $this->createMock(ServerRequestInterface::class); $serverRequest->method('getMethod')->willReturn('GET'); $serverRequest->method('getAttribute')->willReturnMap([ ['referrerId', null, 'brk123'], ['parent', null, 'broker'], ['parentID', null, '456'], ]); $requestHandler = $this->createMock(RequestHandlerInterface::class); // 实例化真实的AddHandler,不要mock $handler = new AddHandler( $this->noteForm, $this->renderer->reveal(), $crmApiService->reveal(), $customHydrator->reveal() ); // 调用真实的process方法 $response = $handler->process($serverRequest, $requestHandler); // 断言返回是HtmlResponse,符合预期 $this->assertInstanceOf(HtmlResponse::class, $response); }
方案2:不修改核心依赖的兼容写法
如果暂时不想改生产代码的注入逻辑,可以把实例化服务的逻辑抽成protected方法,用partial mock替换这两个方法的返回:
第一步 AddHandler新增两个protected方法
protected function getCrmApiService(): CrmApiService { return new CrmApiService(); } protected function getCustomHydrator(): CustomHydrator { return new CustomHydrator(); }
process里的new逻辑改成调用这两个方法即可。
第二步 测试用partial mock替换方法
$mock = $this->getMockBuilder(AddHandler::class) ->onlyMethods(['getCrmApiService', 'getCustomHydrator']) // 只mock这两个方法,保留process的真实逻辑 ->setConstructorArgs([$this->noteForm, $this->renderer->reveal()]) ->getMock(); // 模拟两个服务的返回 $mockCrm = $this->createMock(CrmApiService::class); $mockCrm->method('getUsers')->willReturn('模拟的用户json'); $mockHydrator = $this->createMock(CustomHydrator::class); $mockHydrator->method('getHydrated')->willReturn(['模拟的序列化用户数组']); $mock->method('getCrmApiService')->willReturn($mockCrm); $mock->method('getCustomHydrator')->willReturn($mockHydrator); // 后续直接调用$mock->process(),断言返回值即可
will()相关疑问解答
你现有代码里mock了process方法,加will()只是给这个假的mock方法定义返回值,完全测不到你的业务逻辑,所以不需要加这段配置。你需要保留process的真实实现,直接调用后断言返回的响应类型、状态码、内容等参数即可。
内容的提问来源于stack exchange,提问作者GoharSahi
相关产品推荐
相关产品推荐

