You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Mockery的PHPUnit测试无法拦截被Mock类的方法调用

问题:Mockery无法拦截继承的protected方法init?

我使用PHPUnit 10.5.2和Mockery 1.6.11编写测试用例,测试DocumentsController的mergedDocuments方法。该控制器继承自Ias\AdminController,mergedDocuments方法会调用父类的protected方法init。我通过Mockery对DocumentsController做部分Mock,设置init方法接收指定的mockAdminView并返回true,但实际运行时init方法未被拦截,仍执行原逻辑返回false,导致mergedDocuments提前返回$view。请问为何init方法无法被拦截?

测试代码

public function testMergedDocumentsWithSuccessfulInit() {
    $params = new \Buan\Request\Parameters([], [], [], []);
    $mockAdminView = m::mock(AdminView::class);

    // 考虑继承自Ias\AdminController,对DocumentsController做部分Mock
    $controller = m::mock('DocumentsController', [$params])
        ->makePartial()
        ->shouldAllowMockingProtectedMethods();

    // Mock继承自Ias\AdminController的init方法
    $controller->shouldReceive('init')
        ->with($mockAdminView)
        ->andReturn(true)
        ->once(); // 确保init方法被Mock后返回true一次

    echo "Mock setup complete, testing init call\n";
    $initResult = $controller->init($mockAdminView); // 这里应该返回true,因为init已被Mock
    echo "init called, result: " . ($initResult ? "true" : "false") . "\n";

    // 执行mergedDocuments方法并检查结果
    $result = $controller->mergedDocuments();
}

被测试的mergedDocuments方法片段

public function mergedDocuments(): AdminView
{
    // 初始化
    $view = new AdminView();
    $initResult = $this->init($view);
    if (!$initResult) {
        return $view;
    }
    // 后续业务逻辑
}

init方法定义

protected function init(AdminView $view, bool $bypassAuthCheck = false): bool


原因与解决方案

核心原因

  • 参数不匹配:你在Mock中指定的参数是$mockAdminView,但mergedDocuments方法内部调用init时,传入的是新建的AdminView实例,并非你Mock的对象。Mockery的方法匹配是严格的,参数不匹配时会跳过Mock逻辑,执行原方法。
  • 继承方法的Mock特性:当父类的protected方法未被子类重写时,Mockery的部分Mock对继承方法的拦截需要更精准的匹配规则,否则会优先执行父类的原方法。

解决方案

方案1:调整参数匹配逻辑,兼容实际调用

将Mock的参数匹配改为接受任意AdminView类型的实例,而不是指定某个特定Mock对象:

$controller->shouldReceive('init')
    ->with(m::type(AdminView::class)) // 匹配所有AdminView类型的实例
    ->andReturn(true)
    ->once();

方案2:改造方法,支持注入AdminView(推荐)

修改mergedDocuments方法,允许外部传入AdminView实例,让测试可以直接传入Mock对象:

// 修改后的被测试方法
public function mergedDocuments(AdminView $view = null): AdminView
{
    $view = $view ?? new AdminView(); // 无传入时使用默认实例
    $initResult = $this->init($view);
    if (!$initResult) {
        return $view;
    }
    // 后续业务逻辑
}

// 测试代码中传入Mock的AdminView
$result = $controller->mergedDocuments($mockAdminView);

方案3:明确指定Mock父类的init方法

如果子类没有重写init方法,可以直接指定父类的方法进行Mock:

$controller->shouldReceive('Ias\AdminController::init')
    ->with(m::type(AdminView::class))
    ->andReturn(true)
    ->once();

内容的提问来源于stack exchange,提问作者Tom Arnautovic

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 22:41:00