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

如何让PHP中Mock对象与空合并运算符(??)正常协同工作?

问题:Mockery Mock对象与空合并运算符(??)兼容问题

在使用Mockery编写PHPUnit测试时,遇到空合并运算符(??)与Mock属性协同工作的问题:直接调用$model->title能返回Mock的值,但使用$model->title ?? 'default'时总是触发默认值回退,仿佛属性不存在或为null。

测试代码

public function testExample()
{
    $model = Mockery::mock(Model::class)->shouldIgnoreMissing();

    // Mocking a property
    $model->shouldReceive('getAttribute')
        ->with('title')
        ->andReturn('Test Page');

    $model->title = 'Test Page'; // 尝试过的另一种方式

    $result = $this->doSomething($model);

    $this->assertEquals('/test-page', $result);
}

public function doSomething($model): string
{
    print_r([$model->title, $model->title ?? 'default']);
    // ... 其他逻辑
}

输出结果

Array
(
    [0] => Test Page
    [1] => default
)

环境栈:PHP 8.2,PHPUnit 10.5搭配Laravel 11.33.2

注:无需测试模型或Laravel框架本身,仅需让模型返回特定值,不想使用工厂及关联。


解决方案

原因分析

Laravel模型的属性访问依赖__get魔术方法,但空合并运算符(??)会优先调用__isset方法判断属性是否实际存在于对象中,而非仅检查__get的返回值。Mockery默认不会为Mock的属性注册__isset的返回逻辑,因此??会判定属性不存在,触发默认值。

方法一:Mock __isset 方法

直接为目标属性mock__isset方法,让空合并运算符认为属性存在:

public function testExample()
{
    $model = Mockery::mock(Model::class)->shouldIgnoreMissing();

    // Mock getAttribute 返回指定值
    $model->shouldReceive('getAttribute')
        ->with('title')
        ->andReturn('Test Page');
    
    // Mock __isset 让title被判定为存在
    $model->shouldReceive('__isset')
        ->with('title')
        ->andReturn(true);

    $result = $this->doSomething($model);

    $this->assertEquals('/test-page', $result);
}

方法二:使用Laravel自带的MockBuilder

Laravel的MockBuilder已封装模型魔术方法的兼容逻辑,无需手动处理__isset:

public function testExample()
{
    $model = $this->mock(Model::class, function ($mock) {
        $mock->shouldReceive('getAttribute')
            ->with('title')
            ->andReturn('Test Page');
        // 或直接设置属性,Laravel Mock会自动处理
        $mock->title = 'Test Page';
    });

    $result = $this->doSomething($model);

    $this->assertEquals('/test-page', $result);
}

方法三:用匿名类替代Mock(简单场景首选)

如果仅需让模型返回固定值,直接创建匿名类模拟模型,避开魔术方法问题:

public function testExample()
{
    $model = new class extends Model {
        public $title = 'Test Page';
    };

    $result = $this->doSomething($model);

    $this->assertEquals('/test-page', $result);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 00:22:42