
如何用 ai/test 的 mock provider 对 AI SDK 代码做确定性单元测试【免费下载链接】aiThe AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and agents项目地址: https://gitcode.com/GitHub_Trending/ai/ai如果你的代码用 AI SDK 的generateText、streamText等函数调用语言模型直接写单测会遇到三个问题模型输出不确定、调用慢、调用要花钱。AI SDK Core 为此在ai/test子路径中内置了 mock provider 和测试辅助函数让你在单元测试里控制 AI SDK 的输出做到可重复、确定性地测试而不需要真正调用任何语言模型 provider见 Testing 文档。本文的主路径用MockLanguageModelV4分别给generateText和streamText写确定性测试再用Output测试结构化输出可选分支是模拟 UI Message Stream 响应来测试流协议。准备条件确认 ai/test 子路径可用测试辅助函数来自ai包的./test导出导入方式固定为from ai/test。当前仓库中该导出的定义见 ai 包 package.jsonexports中的./test指向./dist/test/index.js该包当前版本为 7.0.97engines要求node 22。ai/test提供以下辅助函数以 Testing 文档 为准MockEmbeddingModelV4基于 embedding model v4 规范的 mock embedding 模型MockLanguageModelV4基于 language model v4 规范的 mock 语言模型本文主路径使用它mockId提供自增整数 IDmockValues每次调用依次返回数组中的下一个值数组耗尽后返回最后一个值实现见 mock-values.ts。另外simulateReadableStream从主包ai导入不是从ai/test用于带延迟地模拟 readable stream参数说明见 simulateReadableStream 参考chunks必填要依次发出的值数组initialDelayInMs可选首个值发出前的延迟毫秒数默认 0设为null可完全跳过chunkDelayInMs可选值与值之间的延迟毫秒数默认 0设为null可完全跳过返回ReadableStreamT所有 chunk 发出后自动关闭。MockLanguageModelV4的构造参数见 mock-language-model-v4.tsprovider默认mock-providermodelId默认mock-model-iddoGenerate/doStream可以是函数、单个结果对象或结果对象数组。传数组时按调用次序返回对应结果超出数组长度后重复最后一个如果不提供doGenerate/doStream默认值是notImplemented即相应方法未被实现实例会记录doGenerateCalls与doStreamCalls两个数组保存每次调用的入参可用于在断言中核对调用内容。为 generateText 写第一个确定性测试最直接的用法构造MockLanguageModelV4在doGenerate里返回固定的生成结果然后像平常一样调用generateText以下代码来自 Testing 文档import { generateText } from ai; import { MockLanguageModelV4 } from ai/test; const result await generateText({ model: new MockLanguageModelV4({ doGenerate: async () ({ content: [{ type: text, text: Hello, world! }], finishReason: { unified: stop, raw: undefined }, usage: { inputTokens: { total: 10, noCache: 10, cacheRead: undefined, cacheWrite: undefined, }, outputTokens: { total: 20, text: 20, reasoning: undefined, }, }, warnings: [], }), }), prompt: Hello, test!, });这段代码没有任何网络调用doGenerate固定返回文档示例中的Hello, world!文本和固定的 token 用量所以每次运行结果一致——这就是确定性的落点测试断言可以直接写死期望值而不受模型行为影响。为 streamText 写流式测试流式路径用doStream配合simulateReadableStream构造语言模型 v4 的流事件text-start、text-delta、text-end、finishimport { streamText, simulateReadableStream } from ai; import { MockLanguageModelV4 } from ai/test; const result streamText({ model: new MockLanguageModelV4({ doStream: async () ({ stream: simulateReadableStream({ chunks: [ { type: text-start, id: text-1 }, { type: text-delta, id: text-1, delta: Hello }, { type: text-delta, id: text-1, delta: , }, { type: text-delta, id: text-1, delta: world! }, { type: text-end, id: text-1 }, { type: finish, finishReason: { unified: stop, raw: undefined }, logprobs: undefined, usage: { inputTokens: { total: 3, noCache: 3, cacheRead: undefined, cacheWrite: undefined, }, outputTokens: { total: 10, text: 10, reasoning: undefined, }, }, }, ], }), }), }), prompt: Hello, test!, });这里的chunks数组就是文档给出的完整流事件序列示例按顺序发出后流自动结束。如果测试中想模拟 provider 的响应延迟给simulateReadableStream加上initialDelayInMs/chunkDelayInMs即可不需要延迟时保持默认 0 或显式传null。测试带 Output 的结构化输出当业务代码用Output约束结构化输出时mock 的文本内容必须是能通过对应 schema 校验的 JSON 字符串。文档示例用zod定义了{ content: string }schema并让 mock 固定返回对应的 JSON 文本import { generateText, Output } from ai; import { MockLanguageModelV4 } from ai/test; import { z } from zod; const result await generateText({ model: new MockLanguageModelV4({ doGenerate: async () ({ content: [{ type: text, text: {content:Hello, world!} }], finishReason: { unified: stop, raw: undefined }, usage: { inputTokens: { total: 10, noCache: 10, cacheRead: undefined, cacheWrite: undefined, }, outputTokens: { total: 20, text: 20, reasoning: undefined, }, }, warnings: [], }), }), output: Output.object({ schema: z.object({ content: z.string() }) }), prompt: Hello, test!, });streamText的 Output 场景同理把 mock 的 JSON 文本拆成多个text-deltachunk 依次发出文档中给出了拆分为{、content:、Hello,、world、!、}六个 delta 的完整示例。要点是delta 拼接后的完整文本必须与 schema 匹配测试才能走通解析流程。多次调用的场景结果数组与 mockValues两类辅助机制对应两种多调用场景按次序返回不同结果doGenerate/doStream直接传结果数组第 N 次调用返回第 N 个元素数组耗尽后重复最后一个。适合模拟同一会话中多轮模型响应。按次序取中间值mockValues(...values)返回一个函数每次调用取下一个值耗尽后返回最后一个。适合在doGenerate函数体内部为不同调用构造不同的 usage、ID 等字段mockId则用于生成自增 ID。可选分支模拟 UI Message Stream 响应如果你在测试、调试或演示 UI Message Stream 协议可以不用 mock 语言模型直接在 Next 的 route 里返回模拟的 SSE 响应。Testing 文档 给出的示例 route 如下initialDelayInMs: 1000为首个 chunk 前的延迟chunkDelayInMs: 300为 chunk 间延迟文档注释原文import { simulateReadableStream } from ai; export async function POST(req: Request) { return new Response( simulateReadableStream({ initialDelayInMs: 1000, // Delay before the first chunk chunkDelayInMs: 300, // Delay between chunks chunks: [ data: {type:start,messageId:msg-123}\n\n, data: {type:text-start,id:text-1}\n\n, data: {type:text-delta,id:text-1,delta:This}\n\n, data: {type:text-delta,id:text-1,delta: is an}\n\n, data: {type:text-delta,id:text-1,delta: example.}\n\n, data: {type:text-end,id:text-1}\n\n, data: {type:finish}\n\n, data: [DONE]\n\n, ], }).pipeThrough(new TextEncoderStream()), { status: 200, headers: { Content-Type: text/event-stream, Cache-Control: no-cache, Connection: keep-alive, x-vercel-ai-ui-message-stream: v1, }, }, ); }这是一个独立于单测主路径的演示/调试分支仅在你要覆盖 UI Message Stream 协议时使用。结果验证与已知限制确定性即验证方式文档对这些 mock 的定义是可重复、确定性地测试而不实际调用语言模型 provider。因此验证方式就是你的测试断言对固定输出如文档示例中的Hello, world!文本、固定的usage数值——均为文档示例值在多次运行中成立且不需要任何 API key 或网络环境。调用入参可核对mock 实例的doGenerateCalls/doStreamCalls保存了每次调用的LanguageModelV4CallOptions需要断言SDK 传给了模型哪些参数时可以直接检查这两个数组。限制本文示例覆盖的是文档中列出的 v4 mockMockLanguageModelV4、MockEmbeddingModelV4未提供doGenerate/doStream的 mock 对应方法默认是notImplemented不能用于真实调用。simulateReadableStream的chunks为必填项两个延迟参数不传时默认 0。下一步如果要用 mock 覆盖更多功能面可以继续阅读 Testing 文档 中各示例以及 simulateReadableStream 参考。【免费下载链接】aiThe AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and agents项目地址: https://gitcode.com/GitHub_Trending/ai/ai创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考