Laravel中PHPUnit模拟Importer对象失效问题排查
问题:Laravel中模拟Eloquent模型的get_data方法不生效?
我有一个Laravel站点,其中包含名为Importer的模型对象,它继承自Illuminate\Database\Eloquent\Model。该模型的__construct函数接收$name和$type参数,还有一个get_data()方法,会调用外部API并返回SimpleXML对象或null。我需要测试涉及该模型的复杂流程,因此希望在测试中模拟该对象,避免调用外部API,让get_data()返回可测试数据。
我在测试中编写了以下代码:
$this->mock(Importer::class, function ($mock) { $mock->shouldReceive('get_data')->once()->andReturn(null); });
期望当测试中其他对象的方法执行以下代码时,能模拟返回null:
$import_object = new Importer('Weather', 'Temperature'); $result = $import_object->get_data();
但实际情况是,Importer的原get_data()方法仍被调用。我还尝试了以下方式,结果依然相同:
- 通过容器实例化:
$import_object = app(Importer::class, ['asset_name' => $name, 'type' => $type]); $result = $import_object->get_data();
- 通过容器别名实例化:
$import_object = app('Importer', ['asset_name' => $name, 'type' => $type]); $result = $import_object->get_data();
- 使用部分模拟:
$this->mock(Importer::class, function ($mock) { $mock->makePartial()->shouldReceive('get_data')->once()->andReturn(null); });
原因与解决方案
核心原因
Eloquent模型的实例化逻辑特殊,直接用new关键字创建对象不会触发Mockery的模拟替换;同时自定义构造参数的方式可能干扰了容器的模拟绑定逻辑,导致容器返回的还是原类实例而非模拟对象。
正确实现步骤
要让模拟生效,必须确保所有Importer实例都通过容器获取,且容器中绑定的是模拟对象:
- 将模拟对象绑定到容器
在测试方法中,用instance方法把模拟对象绑定到容器,覆盖原Importer类的绑定:
$this->instance(Importer::class, Mockery::mock(Importer::class, function ($mock) { // 模拟get_data方法的返回值 $mock->shouldReceive('get_data')->once()->andReturn(null); // 如果需要匹配构造参数,添加对应的预期 $mock->shouldReceive('__construct')->with('Weather', 'Temperature')->andReturnSelf(); }));
- 统一通过容器获取实例
测试代码中所有创建Importer的地方,都要通过容器解析,不能直接用new:
// 正确方式:容器会返回模拟对象 $import_object = app(Importer::class, ['name' => 'Weather', 'type' => 'Temperature']); $result = $import_object->get_data();
额外注意点
- 如果需要保留模型原构造逻辑,可使用
makePartial()创建部分模拟对象:
$this->instance(Importer::class, Mockery::mock(Importer::class)->makePartial() ->shouldReceive('get_data')->once()->andReturn(null) ->getMock());
- 测试结束后,Laravel测试基类会自动清理Mockery模拟,若出现残留问题,可在
tearDown方法中添加Mockery::close();。
内容的提问来源于stack exchange,提问作者Hoyt Jolly
相关产品推荐
相关产品推荐

