应将ai请求封装为可替换的客户端类,测试时注入返回预设json的fakeaiclient,并通过断言验证其被调用,避免真实网络请求。

用 file_get_contents 模拟请求但不发出去?不行,得换思路
直接调用 file_get_contents 或 cURL 一定会真实发包,哪怕目标是本地地址。想“模拟 AI 接口测试”且“不触发真实调用”,核心不是拦截网络层,而是绕过 HTTP 客户端——把接口逻辑从「发起请求」变成「执行函数」。
常见错误是试图用 mockery 或 phpunit 的 HTTP 模拟去 stub 外部服务,结果发现底层仍依赖 cURL 扩展、或 mock 覆盖不到实际调用链。真正轻量可控的方式,是让业务代码本身支持「可替换的客户端」。
- 把 AI 请求封装成一个独立类(如
AiClient),所有参数构造、headers 设置、JSON 解析都集中在此 - 业务逻辑中不 new
AiClient,而是通过构造函数或 setter 注入实例 - 测试时传入一个继承自
AiClient的FakeAiClient,send()方法直接返回预设 JSON 字符串
如何写一个靠谱的 FakeAiClient?关键在响应结构对齐
很多模拟失败,是因为 fake 返回的 JSON 和真实 AI 接口字段不一致:比如真实返回 {"choices":[{"message":{"content":"xxx"}}]},而你 mock 成 {"text":"xxx"},下游解析就抛 Undefined index: choices。
建议做法:抓一次真实响应(用 Postman 或 curl -v),保存为 fixture 文件(如 tests/fixtures/ai-completion-success.json),然后在 fake 中读取并返回:
class FakeAiClient extends AiClient
{
public function send(array $payload): array
{
return json_decode(
file_get_contents(__DIR__ . '/../fixtures/ai-completion-success.json'),
true
);
}
}
这样既避免硬编码 JSON 字符串易出错,又保证结构完全一致。注意路径要能被测试 autoloader 正确加载。
测试里怎么确保没走真实网络?加个断言防手滑
光写 fake 不够,得验证它真被用了。最简单有效的方式:在真实 AiClient 的 send() 方法开头加一行 throw new RuntimeException('Real AI client invoked in test');,只在测试环境启用(比如通过 defined('PHPUNIT_TEST') 判断)。
或者更稳妥地,在 fake 类里埋个 flag:
class FakeAiClient extends AiClient
{
public static $wasCalled = false;
public function send(array $payload): array
{
self::$wasCalled = true;
// ...
}
}
测试末尾加:$this->assertTrue(FakeAiClient::$wasCalled);。一旦漏掉注入 fake,这个断言就失败,立刻暴露问题。
为什么不用 stream_wrapper_register 拦截 http://?太重且不可靠
有人试过注册自定义 stream wrapper 替换 http:// 协议,让所有 file_get_contents('http://api.example.com') 都返回 mock 数据。这理论上可行,但实际踩坑多:
- PHP 8.0+ 对内置协议拦截更严格,部分场景会跳过 wrapper 直连
- Composer 加载、Xdebug 日志、甚至某些扩展内部的 HTTP 调用也会被误拦
- 无法区分「该 mock 的 AI 请求」和「不该 mock 的第三方监控上报」
比起全局劫持,依赖注入 + fake 类的方案粒度更细、意图更明确、调试更直观。复杂点在于需要改一点原有代码结构——但这是值得的,否则每次加新 AI 功能,测试都要重新 patch 网络层。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











