Mockery 在 Laravel 中必须与容器绑定机制对齐,否则 mock 无效;构造函数注入才生效,new 实例或未清除单例会导致失败;带参解析需手动 bind + spy;Facade 必须用 shouldReceive() 或 fake();partialMock 不跳过构造函数。

Mockery 在 Laravel 里不是“配个 expect 就完事”的玩具——它必须和容器绑定机制对齐,否则你 mock 的对象根本不会被业务代码用上。
为什么 $this->mock() 有时不生效?
常见现象:调用了 $this->mock(UserService::class),但测试中 UserService 依然走真实逻辑,甚至发起 HTTP 请求或查数据库。
根本原因在于依赖获取方式:
- 被测类通过构造函数注入(✅ 容器解析)→
$this->mock()有效 - 被测类里写了
new UserService()(❌ 绕过容器)→ Mockery 完全拦截不到 - 服务注册为
singleton,但测试前没调用$this->app->forgetInstance(UserService::class)→ 容器返回旧单例,mock 被忽略
验证方法:在被测方法开头加 dd(app(UserService::class) === app(UserService::class)),如果输出 true 说明是单例;再检查 mock 后是否仍为 true,不是就说明绑定失败。
如何 mock 带运行时参数的容器解析?
比如你有 app(Transformer::class, [$data]) 这种带第二参数的调用,$this->mock() 或 Mockery::mock() 默认失效。
这是因为 Laravel 容器在检测到非空 $parameters 时,会跳过 instance() 绑定,直接执行原始 bind() 闭包。
正确做法是手动重绑定:
$spy = spy(Transformer::class); app()->bind(Transformer::class, fn () => $spy); // 现在无论 app(Transformer::class) 还是 app(Transformer::class, [$data]) 都返回 $spy
注意:spy() 比 mock() 更适合这种场景——它保留原方法逻辑,只记录调用,避免因未声明 shouldReceive() 导致测试意外失败。
Facade 模拟必须用 shouldReceive(),不能用 instance()
门面(如 Cache、Mail、Pdf)本质是静态代理,$this->app->instance(Cache::class, $mock) 完全无效。
必须用 shouldReceive() 显式拦截静态调用:
Cache::shouldReceive('get')
->with('key')
->andReturn('mocked_value');
// 错误写法(毫无效果)
$mock = Mockery::mock();
$this->app->instance(Cache::class, $mock);
额外提醒:Laravel 9+ 中部分门面(如 Mail、Notification)支持 ::fake(),比 shouldReceive() 更简洁且自带断言能力,优先用 Mail::fake() 而非手动 mock。
部分模拟(partialMock)容易漏掉构造逻辑
当你用 $this->partialMock(EmailService::class) 只想 stub send() 方法,却忘了该类构造函数里有初始化连接池、读配置等副作用——这些仍会真实执行。
解决办法有两个:
- 显式传入构造参数,绕过初始化:
$mock = $this->partialMock(EmailService::class)->makePartial()->shouldIgnoreMissing(); - 更稳妥:改用
Mockery::mock(EmailService::class)->makePartial(),然后手动调用构造逻辑或用shouldAllowMockingProtectedMethods()控制 protected 初始化方法
关键点:partialMock 不等于“禁用构造函数”,它只是不自动 mock 所有方法——构造过程照常发生,这是最容易被忽略的执行泄漏点。


















