You need to enable JavaScript to run this app. 如何使用getMockBuilder()为Mezzio自定义服务实现PHP UnitTest?
优惠活动
大模型
产品
解决方案
定价
更多

如何使用getMockBuilder()为Mezzio自定义服务实现PHP UnitTest?

问题梳理

你现在的测试代码存在核心逻辑错误,且生产代码的写法无法直接mock依赖:

  1. 你把需要测试的核心方法AddHandler::process加入了onlyMethods列表,相当于PHPUnit用假的mock实现替换了真实业务逻辑,测试能通过但完全没有验证你写的代码,属于无效测试。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.15 07:54:03