只要业务代码依赖第三方 API,测试就很容易被网络状态、接口限流和外部服务波动拖慢。Laravel 提供的 Http::fake(),正是为这类场景准备的测试工具:它能拦截应用发出的 HTTP 请求,并返回你预先定义好的响应。
这篇文章不只介绍语法,还会把几个最常用的测试策略拆开说明,包括按 URL 伪造响应、模拟多次返回、根据请求动态回包,以及如何用断言确认请求是否真的按预期发出。读完后,你可以更快判断当前测试该用哪一种 fake 方式,哪些地方又该额外加防护。
Http::fake() 到底解决了什么问题
在测试阶段直接调用真实外部 API,通常会带来三个明显问题:一是测试速度变慢,二是测试结果受网络和第三方服务状态影响,三是某些接口存在调用次数限制,频繁运行测试很快就会碰到上限。
Http::fake() 的作用,就是把这些真实请求拦下来,改为返回伪造响应。这样一来,测试过程不再依赖外部网络,也不需要真的访问第三方服务。
最基础的写法如下:
use IlluminateSupportFacadesHttp;
// 伪造所有HTTP请求
Http::fake();
// 发起请求(将返回空的200响应)
$response = Http::get('https://api.example.com/data');这一写法的含义很直接:后续所有 HTTP 请求都不会真的发出去,而是返回一个空的 200 响应。它适合先把外部依赖整体隔离掉,再逐步补充更精确的响应规则。
几种最常见的伪造场景
按特定 URL 返回固定响应
如果你的代码会调用多个外部服务,最实用的方式通常不是“一刀切”全部返回空响应,而是按域名或路径分别指定结果。这样测试更贴近真实业务,也更容易覆盖不同接口分支。

Http::fake([
// 伪造GitHub API的响应
'github.com/*' => Http::response(['id' => 1, 'name' => 'laravel'], 200),
// 伪造Google API的响应
'google.com/*' => Http::response('Hello World', 200),
]);
// 这将返回伪造的GitHub响应
$response = Http::get('https://github.com/api/users/1');这里的通配符 * 很关键。只要域名符合 github.com/*,对应请求都会命中同一套伪造规则。对于同域名下多个端点的测试,这种写法通常已经足够。
需要多次返回不同结果时,用响应序列
很多外部接口并不是每次都返回同样的数据。例如测试分页抓取、轮询任务状态、首次成功后再次失败,或者重试逻辑是否生效,这时就需要让同一个接口在不同调用次序下返回不同结果。
Http::fake([
'api.example.com/*' => Http::sequence()
->push(['data' => 'first response'], 200)
->push(['data' => 'second response'], 200)
->pushStatus(404), // 最后返回404错误
]);
// 第一次调用返回第一个响应
$first = Http::get('https://api.example.com/data');
// 第二次调用返回第二个响应
$second = Http::get('https://api.example.com/data');
// 第三次调用返回404
$third = Http::get('https://api.example.com/data');这类写法特别适合验证“程序在不同返回值下是否能正确推进逻辑”。相比手动拆成多组测试,序列更适合表达一个连续过程。
复杂条件下,用回调动态决定返回内容
如果固定映射和响应序列还不够,就可以把 Http::fake() 写成回调形式。这样你可以根据请求 URL、方法、请求体甚至请求头,动态决定返回什么。
Http::fake(function ($request) {
// 根据请求内容决定返回什么响应
if (str_contains($request->url(), 'users')) {
return Http::response(['user' => 'John'], 200);
}
return Http::response('Not found', 404);
});这种方式适合请求组合较多、判断条件复杂的场景。比如同一个服务里有多个资源接口,或者不同查询参数会触发不同业务分支。与其为每个路径单独写一条规则,不如把判断逻辑集中到一个回调里,测试意图也更清晰。
怎样防止测试偷偷发出真实请求
很多团队在使用 Http::fake() 时,容易忽略一个问题:并不是每个请求都会自动命中你写好的 fake 规则。一旦代码里多出一个未预期的接口调用,测试就可能在你没注意时访问真实外部服务。

Laravel 为此提供了一个很实用的保护措施:Http::preventStrayRequests()。它的作用是禁止测试中出现未被伪造的 HTTP 请求,一旦发生,直接抛出异常。
// 在测试类的setUp方法中
public function setUp(): void
{
parent::setUp();
Http::preventStrayRequests();
}
// 然后只伪造你期望的请求
Http::fake([
'expected-api.com/*' => Http::response('OK', 200),
]);
// 这将正常执行
Http::get('https://expected-api.com/data');
// 这将抛出异常,因为未伪造此请求
Http::get('https://unexpected-api.com/data');这一步很值得放进测试基类或 setUp 中统一处理。原因很简单:它能把“测试跑过了,但其实偷偷访问了真实 API”的隐患提前暴露出来。对于依赖多个第三方平台的项目,这类保护尤其重要。
只 fake 还不够,还要验证请求是否真的发出了
把响应伪造出来,只能说明你的测试不会走真实网络;但如果要确认业务代码是否真的按预期调用了某个接口,还需要进一步做请求断言。
Laravel 的 HTTP 客户端提供了对应的断言方法,可以检查某个请求有没有发出、请求头是否正确、以及总共发了多少次请求。
Http::fake();
// 执行你的代码,可能会发送HTTP请求
app(CallExternalApi::class)->execute();
// 断言特定请求被发送
Http::assertSent(function ($request) {
return $request->url() === 'https://api.example.com/data' &&
$request->hasHeader('Authorization', 'Bearer token123');
});
// 断言特定请求未被发送
Http::assertNotSent(function ($request) {
return $request->url() === 'https://api.other.com/data';
});
// 断言请求数量
Http::assertSentCount(3);这几种断言分别对应不同层面的测试目标:
Http::assertSent()用来确认某个请求确实被发出,并验证 URL、Header 或参数是否正确;Http::assertNotSent()用来确认某条不该发生的请求确实没有出现;Http::assertSentCount(3)则适合验证重试、循环调用或批量请求逻辑。
如果你的测试只调用了 Http::fake() 却完全不做断言,那么它往往只能证明“代码没有报错”,却未必能证明“代码真的按预期与外部 API 交互”。
实战里更稳妥的使用思路
从日常项目经验看,Http::fake() 最适合承担两类职责:一类是隔离外部依赖,保证测试运行稳定;另一类是精确描述外部接口在不同情况下的返回,从而覆盖成功、失败和异常分支。
如果要把它用得更稳,通常可以遵循下面这套思路:
- 默认先隔离所有外部请求,再按测试目的补充具体 fake 规则;
- 遇到连续状态变化的接口,优先考虑
Http::sequence(); - 请求条件复杂时,使用回调统一处理响应逻辑;
- 在测试初始化阶段加上
Http::preventStrayRequests(),避免遗漏; - 关键业务链路里补上
assertSent、assertNotSent和请求次数断言,避免只测到“表面通过”。
归根到底,Http::fake() 的价值不只是让测试更快,而是让涉及外部 HTTP 调用的代码变得更可控、更可验证。对 Laravel 项目来说,这是提升测试质量非常直接的一步。









