thinkphp服务必须经容器解析才能生效,测试中直接new会绕过容器导致依赖缺失;应使用app()->instance()或tp8的$this->mock()进行mock,并确保被测代码通过容器获取实例。

测试中直接 new 服务会失效
ThinkPHP 的服务必须经容器解析才能获得完整生命周期和依赖注入。你在测试里写 new PaymentService(),它不会触发模型绑定、事件监听或配置注入,save() 或 query() 很可能报 Call to a member function query() on null。这不是服务本身的问题,而是绕过了容器。
正确做法是让被测代码通过容器获取实例——要么构造函数注入,要么用 app(PaymentService::class),否则 mock 和替换都无从谈起。
- 检查控制器或 Service 类的构造方法,确认参数类型是否为接口或具体类,并已注册到容器
- 避免在测试方法里手动 new,哪怕只是为了“快一点”
- 若历史代码大量使用
new,优先在重构时改为依赖注入,再补测试
用 app()->instance() 强制替换容器绑定
这是 ThinkPHP 5.1/6.x/8.x 都可用的底层方式,不依赖框架测试基类,适合精细控制。
在测试方法开头执行:
$mock = Mockery::mock(PaymentService::class);
$mock->shouldReceive('charge')->andReturn(true);
app()->instance(PaymentService::class, $mock);
注意两点:
-
app()->instance()是单次覆盖,只对当前请求生效;tearDown 不用额外清理,但下个测试需重新设置 - 若服务有构造参数(如
__construct(ConfigInterface $config)),mock 实例也得满足签名,否则容器 resolve 失败 - 别漏掉
Mockery::close()—— 虽然 TP 测试基类通常在 tearDown 里做了,但自定义 mock 时建议显式调用
TP8 推荐用 $this->mock()(仅限继承 think\testing\TestCase)
ThinkPHP 8 内置了 TestCase 的 mock() 方法,本质就是封装了 app()->instance() + Mockery 链式期望,更简洁。
示例:
public function test_order_payment_success()
{
$this->mock(PaymentService::class, function ($mock) {
$mock->shouldReceive('charge')->with(199.0)->andReturn(true);
});
$response = $this->post('/api/order', ['amount' => 199.0]);
$response->assertJson(['status' => 'paid']);
}
关键约束:
- 必须确保被测控制器是通过
app(PaymentService::class)或构造注入拿到该服务的,否则 mock 完全不生效 - TP8 的
mock()默认只替换 singleton 绑定;如果服务是每次make()新建的,得配合app()->bind()改为 singleton - 不要在
setUp()里全局 mock,容易污染其他测试;每个测试按需单独 mock
替换 Facade 服务要额外处理静态调用
像 Cache::get()、Log::info() 这类 Facade 调用,不是走容器 app(),而是走静态门面代理。直接 app()->instance() 不起作用。
正确方式是拦截 Facade 的底层实例:
$mock = Mockery::mock(\think\Cache::class);
$mock->shouldReceive('get')->with('user_123')->andReturn(['name' => 'Tom']);
\think\Facade\Cache::shouldReceive('get')->andReturnUsing(function ($key) use ($mock) {
return $mock->get($key);
});
或者更稳妥的做法:在测试前用 \think\Facade\Cache::clearResolvedInstance() 清空缓存,再用 \think\Facade\Cache::setFacadeApplication($app) 注入带 mock 的应用实例。
这类操作容易出错,建议优先把 Facade 调用抽成普通服务类,再走容器注入——既利于测试,也符合 SOLID 原则。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











