Laravel 6单元测试中模拟服务容器实例的核心是接管解析行为,推荐方式依次为:1. 用$this->mock()替换依赖类;2. 用app()->bind()覆盖所有解析路径;3. 用Facade::fake()替代门面mock;4. 手动创建Mockery对象注入容器。

在 Laravel 6 的单元测试中模拟服务容器实例,核心是让容器在解析某个类时返回你预设的模拟对象(mock/spy),而不是真实实例。关键不在于“绕过容器”,而在于“接管容器的解析行为”。以下几种方式按推荐优先级展开,覆盖常见场景。
用 $this->mock() 快速替换依赖类
这是最常用、最安全的方式,适用于通过构造函数或方法注入、且类已由容器管理(如绑定为 singleton 或自动解析)的情况。
- 测试基类
TestCase提供了$this->mock()方法,内部自动调用app()->instance()注册模拟实例 - 它只影响
app(MyClass::class)这类无参解析,对带运行时参数的app(MyClass::class, [$data])无效 - 示例:
$this->mock(PaymentService::class, function ($mock) {
$mock->shouldReceive('charge')->with(100)->andReturn(true);
});
之后任何通过 DI 获取 PaymentService 的地方(如控制器构造函数)都会拿到这个 mock。
用 app()->bind() 强制覆盖所有解析路径
当被测代码使用了带参数的容器解析(如 app(MyClass::class, [$config])),常规 mock() 或 spy() 会失效——因为 Laravel 此时跳过单例缓存,直接执行原始绑定逻辑。
- 解决方案:在测试中用
app()->bind()显式重绑定该类,使其无论是否传参都返回同一个 mock 实例 - 该操作在每次测试后自动重置(Laravel 测试环境默认刷新应用),不会污染其他测试
- 示例:
app()->bind(MyClass::class, fn() => $spy);
// 现在 app(MyClass::class) 和 app(MyClass::class, ['foo']) 都返回 $spy
用 facade fake() 替代手动 mock 静态门面
如果你的代码调用的是 Cache::get()、Mail::to()、Event::dispatch() 等门面方法,不要用 shouldReceive()——它不稳定且容易报错。
- Laravel 提供了轻量稳定的
fake()方法:Cache::fake()、Mail::fake()、Event::fake()、Bus::fake()、Http::fake() - 它们全局拦截调用,支持精准断言(如
Mail::assertSent()、Http::assertSent()),且天然兼容所有调用方式(静态/动态/带参数) - 示例:
$this->post('/subscribe', ['email' => 'test@example.com']);
Mail::assertSent(WelcomeEmail::class);
手动创建 Mockery 对象并注入容器
仅在需要精细控制行为(如抛出异常、链式调用、复杂条件返回)且内置方法无法满足时使用。
- 必须先创建 mock,再用
$this->app->instance()注入,顺序不能反 - 若类未在容器中显式绑定(比如只是普通 class,没被
bind()或singleton()声明),需先补绑定:$this->app->bind(MyClass::class, MyClass::class) - 示例:
$mock->expects('charge')->andThrows(new ConnectionException());
$this->app->instance(PaymentGateway::class, $mock);


















