跳转到主要内容
在包中调用外部 API 时,需要同时做到继承由 Laravel 管理的 HTTP 客户端配置,以及不把每次调用的配置带到下一次调用。如果自行创建 Factory,或长期持有尚未发送的请求,就会导致使用方的 fake 无法生效。 本页面基于对 Laravel Framework v13.39.0 实现的确认。基本操作请参阅HTTP 客户端,测试环境的准备请参阅包的测试。

共享 Factory,每次创建 PendingRequest

Laravel 的 HTTP 客户端中,存在承担不同职责的对象。 FoundationServiceProvider 将 Factory 注册为 singleton,Http Facade 也使用相同的类名从容器中解析。因此,在普通的 Laravel 应用中,针对从容器获取的 Factory 发出的请求,同样可以用 Http::fake() 验证。直接使用 Facade 的设计也可行,但本页面为了明确依赖关系,采用注入 Factory 的方式。 对 Factory 调用 baseUrl() 等普通请求配置方法时,会通过 __call() 转发到新的 PendingRequest。createPendingRequest() 会传入创建时的 fake 回调和 stray request 限制,newPendingRequest() 则会应用公共中间件和公共选项。
即使在包内创建 new Factory() 或其他 HTTP 库的客户端,也不会继承共享 Factory 的 fake 和 Http::preventStrayRequests()。这些功能并不会阻断整个进程的通信。面向 Laravel 的客户端应使用共享 Factory;如果提供独立的客户端,请为该客户端准备专门的测试方法。

将 API 操作封装到小型客户端中

以从虚构的目录 API 获取商品的 Acme\Catalog 包为例。src/CatalogClient.php 只持有 Factory 和基础 URL,请求则在 product() 执行期间创建。
在这种设计下,即使用同一个客户端先调用 product(42, 'ja') 再调用 product(42),上一次调用添加的 Accept-Language 也不会残留。使用方应用在 Factory 上设置的公共配置,仍会继续应用到每个请求。
  • 包专属的配置应用到新创建的 PendingRequest 上。如果在包初始化时设置 Http::globalOptions(),会影响无关的通信,因此应避免。
  • 不要在构造函数中发送请求。设计时要确保仅仅是解析服务、启动 Artisan 或公开配置文件时,不会连接外部 API。
  • throw() 会将 4xx、5xx 转换为 RequestException。要与连接失败或超时产生的 ConnectionException 区分开,不要把失败替换为空数组等成功值。
  • json(flags: JSON_THROW_ON_ERROR) 会将无效的 JSON 转换为 JsonException。示例中只检查了是否为数组,在实际 API 中还应验证必需字段和数据类型。
如果只是统一基础 URL 或请求头,官方文档中的 HTTP 宏也是一种选择。如果要以包的形式提供 API 操作、返回值检查乃至异常契约,拆分为这样的专用类会更便于维护。宏的机制请参阅Macroable trait。

在服务提供者中注册

在包的 config/catalog.php 中准备默认配置。这里不要保存 HTTP 客户端或闭包,只放置可以存入配置缓存的值。
src/CatalogServiceProvider.php 会合并配置,并在解析客户端时传入共享 Factory。不要覆盖 Factory 本身的绑定。
服务提供者可以通过包的自动发现等方式加载。使用者公开配置后,将 config/acme-catalog.php 中的基础 URL 改为实际的 API。示例中的 catalog.example.test 是用于说明和 fake 的虚构主机。
使用方通过构造函数注入或容器来解析客户端。直到这次调用时,才会真正发送获取商品的请求。
注册时使用的是 bind(),但如果持有客户端的一方是 singleton,那么该引用并不会每次都重新创建。关键在于不要把调用专属的语言、认证信息、请求对象保存到属性中。详情请参阅包服务的生命周期设计。 配置的覆盖以及部署时的缓存更新,请参阅包配置的合并与缓存。基础 URL 应作为可信的应用配置进行管理,不要允许通过请求输入随意指定连接目标。

用 fake 验证真实的客户端

如果直接模拟客户端本身,就无法确认它是否使用了共享 Factory,也无法确认 URL 和请求头是否组装正确。在包的测试中,从容器解析真实的 CatalogClient,只把 HTTP 响应设为 fake。 下面是在Orchestra Testbench 测试环境中运行的示例。在每个测试的 setUp() 中启用 preventStrayRequests()。在会发出请求的测试中,只为预期的 URL 设置响应,不设置 '*' 的回退,这样也能检测出 URL 错误。
第二个测试中,先解析客户端,再设置 fake。由于客户端持有 Factory,并在实际调用时才创建 PendingRequest,所以即使按这个顺序,fake 也会生效。如果改成在构造函数中创建并持有 PendingRequest 的设计,就能检测出创建之后添加的 fake 不会生效的问题。 assertSent() 验证的是至少存在一个匹配的请求。为了不遗漏意外的额外发送,还应配合使用 assertSentCount()。assertSentInOrder() 会验证数量和顺序,因此也可用于验证连续使用同一个客户端时的状态隔离。 防护的设置位于 parent::setUp() 之后。仅靠这个测试无法验证服务提供者启动期间的通信,因此要保持注册和启动阶段都不发出请求的设计。
fake 能确认的是客户端组装的请求,以及针对所准备响应的处理。它并不能保证真实服务的认证、TLS 连接、实际的超时时间,乃至外部 API 的规格变更。涉及外部连接的验证,应在明确准备好的验证环境中另行进行。

长期维护中应追加的验证

将共享 Factory 与各个 PendingRequest 的职责分开后,使用方可以直接沿用 Laravel 常规的测试方法。与其先构建自己的 fake 机制,不如先准备经过共享 Factory 的实现,以及验证它的测试。

参考的一手资料

最后修改于 2026年10月27日