facade 必须用 shouldreceive() 拦截静态方法调用,不能用 $this->app->instance() 替换;mock 前需确保未初始化,测试后须调用 clearresolvedinstance() 清理静态缓存。

Facade 不能用 $this->app->instance() 替换
Facade 是静态代理,背后调用的是容器里绑定的服务实例,但调用链是 Cache::get() → Cache::getFacadeRoot() → 容器解析。直接 $this->app->instance(Cache::class, $mock) 不生效,因为 Facade 根本不走这个绑定路径。
常见错误现象:mock 后调用 Cache::get('key') 依然访问真实 Redis,断点进不去 mock 方法。
- 必须用
shouldReceive()拦截 Facade 的静态方法调用 - ThinkPHP 的 Facade 类(如
think\facade\Cache)支持shouldReceive(),这是它对 Mockery 的兼容设计 - 调用前要确保 Facade 尚未被初始化过,否则缓存的 root 实例会跳过拦截 —— 建议在测试方法开头就 mock
shouldReceive() 怎么写才不漏掉调用
Facade mock 的核心是声明「期望哪些方法被调用、返回什么、调用几次」。不写 shouldReceive(),Mockery 默认放行所有调用,等于没 mock。
示例:拦截 Cache::get() 返回固定值,同时验证 Cache::set() 是否被调用:
$this->mock(\think\facade\Cache::class)
->shouldReceive('get')
->with('user:123')
->andReturn(['id' => 123, 'name' => 'test']);
$this->mock(\think\facade\Cache::class)
->shouldReceive('set')
->with('user:123', ['id' => 123, 'name' => 'test'], 3600)
->once();
- 每个
shouldReceive()对应一个方法,不能合并写多个方法到一次链式调用里 -
andReturn()和andThrow()必须显式指定,否则默认返回null -
once()/times(2)等约束建议加上,避免因逻辑变更导致 mock “悄悄失效”
测试结束后要不要清理 Facade mock
要。ThinkPHP 的 Facade 有静态属性缓存 root 实例,如果一个测试 mock 了 Cache,下一个测试没重置,可能沿用上一个 mock 实例,造成干扰。
推荐在 tearDown() 里重置:
protected function tearDown(): void
{
parent::tearDown();
\think\facade\Cache::clearResolvedInstance();
}
- 每个 Facade 类都有
clearResolvedInstance()静态方法,专为测试清理设计 - 不要依赖全局
Mockery::close(),它只清 mock 对象,不清 Facade 的静态缓存 - 如果测试中用了多个 Facade(如
Log、Db),每个都要单独clearResolvedInstance()
为什么不用 think\testing\TestCase 自带的 mock() 方法
可以,但要注意它只是封装了 Mockery::mock() + shouldReceive(),底层仍走 Facade 的拦截机制。它的便利性在于自动处理命名空间和类名映射,比如:
$this->mock('cache')->shouldReceive('get')->andReturn([]);
等价于:
$this->mock(\think\facade\Cache::class)->shouldReceive('get')->andReturn([]);
- 字符串别名(如
'cache')仅限框架内置 Facade,自定义 Facade 必须传完整类名 - 该方法不支持链式多次
shouldReceive(),复杂场景还是直接操作 Facade 类更可控 - 别名方式容易掩盖类名拼写错误(比如写成
'cach'),运行时才报错,不如直接写类名早暴露问题
Cache::get(),你只 mock 了一次 shouldReceive('get'),第二次就会 fallback 到真实实现 —— 这种隐式行为很难 debug,务必按实际调用次数写约束。php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











