Mockery 在 Laravel 中必须与容器绑定机制对齐,否则带参数的 app($class, [$data]) 解析会绕过模拟;mock() 拦截方法执行,spy() 放行原逻辑仅记录调用;需隔离副作用用 mock(),仅验证调用用 spy();带参解析需显式 bind() 重绑定;Facade 优先用 fake(),自定义场景用 shouldReceive();模拟对象必须注入容器才生效。

Mockery 在 Laravel 中不是“加个装饰就能用”的工具,它必须和容器绑定机制对齐,否则 app(MyClass::class, [$data]) 这类带参数的解析会直接绕过模拟,返回真实对象。
mock() 和 spy() 的行为差异直接影响测试是否可靠
mock() 拦截方法调用并阻止原逻辑执行;spy() 则放行原方法(包括副作用),只记录调用痕迹。如果你 mock 的类里有 file_put_contents() 或 Http::post(),用 spy() 却没意识到它仍会真实执行,测试就可能污染文件系统或触发真实 API 请求。
- 需要完全隔离依赖(如跳过支付网关调用)→ 用
mock() - 只想验证某方法是否被调用、传了什么参数,但允许其他逻辑照常运行 → 用
spy() - 两者都必须在测试结束前由 Mockery 自动清理,Laravel 的
RefreshApplicationtrait 已帮你做了,不用手动Mockery::close()
带参数的容器解析(app(‘Class’, [$data]))会绕过常规 mock
Laravel 容器对 app($abstract, $parameters) 有明确优先级:只要 $parameters 非空,就会忽略所有 instance() 或 singleton() 绑定,转而执行你注册的闭包绑定(比如 app()->bind(MyClass::class, fn($app, $p) => new MyClass($p[0])))。所以 $this->mock(MyClass::class) 对这种场景完全无效。
- 错误做法:只调用
$this->mock(MyClass::class)然后期待app(MyClass::class, [$data])返回 mock 实例 - 正确做法:在测试中显式重绑定 ——
app()->bind(MyClass::class, fn() => $mock),强制所有解析路径(含带参)都命中同一实例 - 注意:重绑定写在测试方法内即可,
RefreshApplication保证每次测试都是干净容器
Facade 模拟要分清 fake() 和 shouldReceive() 的适用边界
Mail::fake() 和 Event::fake() 是 Laravel 封装好的零配置方案,覆盖整个门面行为;而 Cache::shouldReceive('get') 属于 Mockery 原生方式,更灵活但也更易出错——比如漏写 use Illuminate\Support\Facades\Cache;,或在非 Laravel 测试环境里误用(此时 Cache 可能未被 facade 代理)。
- 优先用
fake():Mail、Notification、Event、Queue、Cache(Laravel 9.2+)、HTTP(Laravel 9.3+)等已支持 - 只能用
shouldReceive():自定义门面、旧版 Laravel 不支持fake()的门面、或需精确断言调用次数/参数顺序 - 混用风险:
Mail::fake()后再调Mail::shouldReceive()会报错,因为fake()已替换底层 resolver
最常被忽略的一点是:Mockery 模拟对象本身不参与 Laravel 的依赖注入自动解析,除非你把它真正“塞进容器”。光创建 $mock = Mockery::mock(MyService::class) 没用,必须配合 $this->instance()、$this->mock() 或 app()->bind() 才能让控制器或服务实际拿到它。


















