mock()是封装instance()的语法糖,本质都通过容器绑定生效;真正决定Mock是否起效的是被测代码是否从容器获取实例,而非方法调用本身。

mock() 和 instance() 的本质区别在哪
很多人以为 mock() 是“自动注入”,其实它只是语法糖:内部仍调用 instance() 完成容器绑定。真正起作用的是绑定动作本身,不是方法名。如果你在测试中先调用了 mock(Service::class),但控制器里是 new Service(),Mock 依然不会生效——因为没走容器解析。
关键判断点:只有被容器解析的实例(比如构造函数注入、app(Service::class)、或类型提示自动注入)才受 instance() 影响。
-
mock():封装了instance()+ Mockery 实例创建,适合快速覆盖单个类 -
instance():更底层,可传入任意对象(包括手动 new 的 Mockery 对象、spy、甚至真实对象),控制粒度更细 - 两者都只影响当前测试生命周期,Laravel 测试环境会重置容器,无需手动清理
为什么 controller 测试中 mock() 有时不生效
最常见原因是控制器依赖没有走容器。比如写成 $service = new Service() 或通过 resolve() / app()->make() 时传了参数(如 app(Service::class, ['arg' => $x])),而你只调用了 mock(Service::class) ——这种带参解析默认绕过标准绑定,mock() 不会拦截。
解决方式不是换工具,而是统一入口:确保所有依赖都由容器无参解析。如果必须带参,改用 app()->bind(Service::class, fn() => $mock) 显式覆盖。
- 检查控制器构造函数是否用了类型提示(
Service $service),而不是手动 new - 避免在测试中用
resolve(Service::class),改用app(Service::class) - 若服务类有 __construct 参数且无法删减,优先在
AppServiceProvider中绑定为闭包,测试时再用instance()覆盖
instance() 替换依赖的实操要点
instance() 的核心是“替换容器对某类的解析结果”,它不关心你给的是 Mockery 对象、spy 还是 faker 数据,只要类型一致就生效。但它要求你在被测逻辑执行前完成绑定。
典型误操作:在 $response = $this->get(...) 之后才调用 instance() ——此时请求已走完,Mock 毫无意义。
- 绑定必须在发起 HTTP 请求或调用被测方法之前完成
- 参数必须是
Service::class(字符串),不能是'App\Services\Service'字面量(容易拼错) - 如果被模拟类有依赖其他类,别忘了它们也要被容器管理,否则可能触发真实构造
- 使用
spy()时,instance()同样适用:$this->instance(Service::class, $this->spy(Service::class))
partialMock 不等于 instance(),别混用
partialMock() 是 Laravel 提供的快捷方法,它内部先创建 Mockery partial mock,再调用 instance()。但它有个隐藏限制:只对无参解析生效。一旦你的代码里出现 app(Service::class, [...]),partialMock() 就失效,而 instance() 仍可控。
所以当你需要拦截特定方法、又不确定调用方式是否带参时,直接上 instance() 更稳。
-
partialMock(Service::class)->shouldReceive('getData')→ 仅适用于标准容器解析 -
$mock = Mockery::mock(Service::class)->makePartial(); $this->instance(Service::class, $mock)→ 全局生效,含带参场景 - partialMock 无法 stub 构造函数行为;
instance()配合手动 Mockery 可以做到
new Service(),整条链路的 Mock 就断了。查问题时优先 grep 控制器和服务类里的 new ,比反复调 mock() 参数有效得多。


















