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

为何模拟合法HasMany关联后过滤关联集合仍为空?

问题

我正在编写单元测试,用来过滤模型中的无效关联,只保留模型实际存在的关联。但测试失败了——过滤后的关联集合是空的,尽管我已经模拟了其中一个关联validRelation,让它返回合法的HasMany关联。

额外背景

  • 使用Laravel和Mockery进行单元测试。
  • filterRestrictedRelations方法为protected,通过反射调用。
  • 尝试将该方法设为public并移除反射逻辑后,集合仍为空,问题似乎出在Mockery对shouldReceive('validRelation')的处理上。

核心代码

filterRestrictedRelations方法:

protected function filterRestrictedRelations(): void
{
    $this->restrictedRelations = $this->restrictedRelations->filter(function ($relation) {
        $exists = method_exists($this->model, $relation);
        dump("Checking relation: $relation, exists: " . ($exists ? 'true' : 'false'));
        return $exists;
    });
}

测试代码:

public function setUp(): void
{
    parent::setUp();
    $this->model = \Mockery::mock(AnAbstractClass::class)->makePartial();
    $this->modelRelation   = \Mockery::mock(new ModelRelation($this->model));
    $this->reflectionClass = new ReflectionClass($this->modelRelation);
}


public function it_filters_out_relations_that_do_not_exist_in_model(): void
{
    $relations = ['validRelation', 'invalidRelation1', 'invalidRelation2'];

    // Mocking model to have the 'validRelation' method returning a HasMany relation
    $this->model->shouldReceive('validRelation')
                ->andReturn(\Mockery::mock(HasMany::class));

    $this->modelRelation = new ModelRelation($this->model);
    $this->modelRelation->setRestrictedRelations($relations);

    $this->reflectionClass = new ReflectionClass($this->modelRelation);

    // Invoke the filterRestrictedRelations method
    $this->reflectionClass->getMethod('filterRestrictedRelations')
                          ->invoke($this->modelRelation);

    $filteredRelations = $this->reflectionClass->getMethod('getRestrictedRelations')->invoke($this->modelRelation);

    // Check that invalidRelation1 remains, while others are filtered out
    $this->assertEquals(['validRelation'], $filteredRelations->all());
}
原因分析

Mockery的shouldReceive()并不会真正在模拟对象上创建对应的方法,它只是设置了方法调用的预期规则。而method_exists()是PHP原生函数,只会检查对象的类结构中是否存在该方法,完全识别不了Mockery动态设置的方法预期,所以哪怕你用shouldReceive('validRelation')做了模拟,method_exists($this->model, 'validRelation')依然会返回false,最终导致所有关联都被过滤清空。

另外测试代码里还有个小问题:setUp中你mock了modelRelation,但测试方法里又重新实例化了new ModelRelation($this->model),不过这不是导致集合为空的核心原因。

解决方案

针对这个问题,有两种可行的解决思路:

思路1:让模拟对象真正拥有目标方法

如果是用makePartial()创建的模拟对象,可以直接给它添加方法定义,而非仅设置调用预期:

// 替换原来的shouldReceive代码
$this->model = \Mockery::mock(AnAbstractClass::class.'[validRelation]')->makePartial();
$this->model->shouldReceive('validRelation')->andReturn(\Mockery::mock(HasMany::class));

这种方式下,method_exists()就能检测到validRelation方法存在,过滤逻辑会正常保留该关联。

思路2:修改过滤逻辑适配测试场景

如果不想调整模拟方式,可以把method_exists()替换成Laravel Eloquent模型自带的hasRelation()方法:

// 修改filterRestrictedRelations中的判断逻辑
$exists = $this->model->hasRelation($relation);

同时在测试中手动给模型添加关联记录:

// 在测试中添加关联到模型的relations属性
$this->model->relations['validRelation'] = \Mockery::mock(HasMany::class);

hasRelation()会检查模型的$relations属性,这样就能正确识别关联是否存在,过滤逻辑也能正常工作。

测试代码小修正

另外注意测试里的注释写错了,应该是“只保留validRelation,其他关联被过滤掉”,和断言逻辑保持一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 13:16:11