
在 laravel 单元测试中,直接对 eloquent 模型进行部分模拟(partial mock)时,若仍调用真实方法,通常是因为未正确使用 mockery 的 `makepartial()`,或错误地通过 `new order()` 实例化对象而非获取容器中已绑定的模拟实例。
要成功实现模型的部分模拟(即仅拦截特定方法如 save(),其余方法保留原始逻辑),关键在于两点:正确创建部分模拟对象,以及确保测试代码实际使用该模拟实例,而非重新 new 一个真实模型。
首先,Mockery::mock("App\Order[save]") 的语法虽曾用于旧版 Mockery,但在当前主流版本(如 Mockery 1.x + Laravel 9/10)中已不推荐;更可靠、语义更清晰的方式是显式调用 ->makePartial():
$mock = Mockery::mock(Order::class)->makePartial();
其次,$this->app->instance("App\Order", $mock) 仅将模拟对象绑定到服务容器中,并不会自动影响 new Order() 的行为——因为 new 是 PHP 原生实例化,完全绕过 Laravel 容器。因此,原测试中 $order = new Order() 创建的是真实类实例,其 save() 方法自然不会被拦截。
✅ 正确做法是:直接操作 $mock 实例,或通过容器解析获取(如 app(Order::class)),从而确保使用的是已配置好的部分模拟对象。
以下是推荐的完整写法(兼容 Laravel 9+ 和 Mockery 1.6+):
makePartial();
// 预期 save() 被调用一次,返回 5
$mock->shouldReceive('save')->once()->andReturn(5);
// 将模拟实例绑定至容器,供后续依赖注入使用(可选,本例中直接调用 mock 即可)
$this->app->instance(Order::class, $mock);
// ✅ 正确:直接调用 mock 实例的方法
$result = $mock->save();
$this->assertEquals(5, $result);
}
}⚠️ 注意事项:
- 避免 new Order():它永远创建真实实例,与容器绑定无关;
- makePartial() 是核心:它使模拟对象继承原类所有方法,仅对 shouldReceive() 声明的方法进行拦截;
- 及时清理 Mockery:在 tearDown() 中调用 Mockery::close(),防止模拟对象泄漏影响其他测试;
- 命名空间一致性:确认 Order 类的实际命名空间(如 App\Models\Order),并在 mock() 中准确传入;
- 替代方案考虑:对于简单场景,也可使用 Laravel 内置的 Mockery::spy() 或更现代的「测试替身(test double)」策略(如用内存数据库 + DatabaseTransactions),但部分模拟适用于需隔离副作用(如网络请求、文件写入)的 save() 扩展逻辑。
总结:Laravel 模型的部分模拟失败,根源常在于混淆了「容器绑定」与「对象实例化」机制。牢记——绑定是为了让依赖注入生效,而测试中若需控制行为,应直接操作模拟对象本身。










