使用RefreshDatabase Trait时出现Could not load mock: class already exists错误
问题描述
测试代码如下:
public function testSomething() { $this->login(); m::mock('overload:MyClass', function ($mock) { $mock->shouldReceive('METHOD') ->andReturn( (object)[ 'status' => true, 'message' => 'Message', ] ); }); $this->post(route('ROUTE')) ->assertRedirect() ->assertSessionHas( 'success', 'Message' ); }
运行测试时出现以下差异:
- 使用
use DatabaseTransactions:测试正常执行 - 使用
use RefreshDatabase:测试失败,报错信息:Mockery\Exception\RuntimeException : Could not load mock \MyClass, class already exists
环境版本:
- Laravel 5.5
- PHPUnit 6.5.14
已尝试方案:
- 在测试类添加
@runTestsInSeparateProcesses注解 - 在单个测试方法添加
@runInSeparateProcess注解
问题原因
RefreshDatabase 特性会在测试启动阶段就执行数据库迁移操作,这个过程中可能已经触发了 MyClass 的自动加载。而 Mockery 的 overload: 语法要求必须在类被加载前创建 mock,否则就会抛出「类已存在」的错误。
反观 DatabaseTransactions,它是在测试方法执行阶段才启动数据库事务,mock 创建的时机早于 MyClass 的加载,因此可以正常工作。
解决方案
方案1:调整 Mock 创建时机,提前到类加载前
在测试类的 setUp 方法中,先创建 mock,再执行父类的 setUp(即 RefreshDatabase 的迁移逻辑):
protected function setUp() { // 先创建mock,确保MyClass还没被加载 m::mock('overload:MyClass', function ($mock) { $mock->shouldReceive('METHOD') ->andReturn( (object)[ 'status' => true, 'message' => 'Message', ] ); }); parent::setUp(); }
之后测试方法中无需重复编写 mock 代码。
方案2:利用 Laravel 容器替换实例(更推荐)
放弃 Mockery 的 overload: 语法,改用 Laravel 容器绑定注入 mock 实例,这种方式不受类是否已加载的限制:
public function testSomething() { $this->login(); // 创建MyClass的mock $mock = m::mock(MyClass::class); $mock->shouldReceive('METHOD') ->andReturn( (object)[ 'status' => true, 'message' => 'Message', ] ); // 将mock绑定到容器,后续系统会从容器获取实例 $this->app->instance(MyClass::class, $mock); $this->post(route('ROUTE')) ->assertRedirect() ->assertSessionHas( 'success', 'Message' ); }
内容的提问来源于stack exchange,提问作者Ali Raza
相关产品推荐
相关产品推荐

