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() 则会应用公共中间件和公共选项。
将 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 中还应验证必需字段和数据类型。
在服务提供者中注册
在包的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 错误。
assertSent() 验证的是至少存在一个匹配的请求。为了不遗漏意外的额外发送,还应配合使用 assertSentCount()。assertSentInOrder() 会验证数量和顺序,因此也可用于验证连续使用同一个客户端时的状态隔离。
防护的设置位于 parent::setUp() 之后。仅靠这个测试无法验证服务提供者启动期间的通信,因此要保持注册和启动阶段都不发出请求的设计。
fake 能确认的是客户端组装的请求,以及针对所准备响应的处理。它并不能保证真实服务的认证、TLS 连接、实际的超时时间,乃至外部 API 的规格变更。涉及外部连接的验证,应在明确准备好的验证环境中另行进行。
长期维护中应追加的验证
将共享 Factory 与各个 PendingRequest 的职责分开后,使用方可以直接沿用 Laravel 常规的测试方法。与其先构建自己的 fake 机制,不如先准备经过共享 Factory 的实现,以及验证它的测试。
参考的一手资料
- Laravel 官方文档:HTTP 客户端
- Laravel 官方文档:包开发
- Framework v13.39.0:Factory 的 fake、createPendingRequest、__call
- Framework v13.39.0:PendingRequest 的配置与 stub 处理
- Framework v13.39.0:Response 的 throw 与 JSON 解码
- Framework v13.39.0:Http Facade 的解析目标与 fake
- Framework v13.39.0:FoundationServiceProvider 的 singleton 注册
- Laravel 官方测试:HttpClientTest