Test AI SDK v7 agents
Use AI SDK v7's public test surface as the source of truth. Inspect the installed ai/test exports and @ai-sdk/provider V4 types before inventing fixtures; v7 changes can make plausible older examples silently wrong.
Repository test contract
- Reproduce a bug live against the real API before writing the regression test.
- Prefer integration tests that exercise the complete agent flow.
- Run tests with
nx run <project>:test; use node --test --no-warnings path/to/file.test.ts only for the narrow reproduction or filtered test.
- Run type checks with
nx run <project>:typecheck, never raw tsc.
- Import the package under test by its package specifier, not a relative source path.
- Use
node:assert and group related observations in assert.deepStrictEqual when that makes the failure easier to read.
v7-only rules
- Use V4 mocks (
MockLanguageModelV4, MockEmbeddingModelV4, and the other *V4 classes). Do not preserve V3 compatibility.
- Import test models and helpers from
ai/test. Import simulateReadableStream from ai; its ai/test re-export is deprecated.
- Use the built-ins instead of local substitutes:
- sequential return values:
mockValues(...)
- deterministic IDs:
mockId({ prefix })
- stream collection:
convertReadableStreamToArray(stream)
- stream construction:
convertArrayToReadableStream(values) or simulateReadableStream(...)
- async iterable construction:
convertArrayToAsyncIterable(values)
- language prompt/settings capture:
model.doGenerateCalls and model.doStreamCalls
- embedding call capture:
model.doEmbedCalls
MockLanguageModelV4 accepts a function, one result, or an array of results for doGenerate and doStream. Arrays are zero-indexed and return item 0 on the first call.
A fixed doStream result contains one ReadableStream and is therefore single-use. Use function form to construct a fresh stream when the same mock can be called more than once, or provide an array containing a distinct stream per expected call.
- Do not add
rawCall; it is not part of LanguageModelV4StreamResult.
- Do not cast fixtures to
any or never to hide protocol errors. Use satisfies LanguageModelV4GenerateResult, LanguageModelV4StreamResult, or LanguageModelV4StreamPart[] so drift fails at compile time.
Default language-model fixture
import type { LanguageModelV4GenerateResult } from '@ai-sdk/provider';
import { generateText } from 'ai';
import { MockLanguageModelV4 } from 'ai/test';
import assert from 'node:assert';
import { test } from 'node:test';
const usage = {
inputTokens: { total: 3, noCache: 3, cacheRead: 0, cacheWrite: 0 },
outputTokens: { total: 4, text: 4, reasoning: 0 },
} as const;
const response = {
content: [{ type: 'text', text: 'A short summary.' }],
finishReason: { unified: 'stop', raw: 'stop' },
usage,
warnings: [],
} satisfies LanguageModelV4GenerateResult;
test('returns the generated summary', async () => {
const model = new MockLanguageModelV4({ doGenerate: response });
const result = await generateText({ model, prompt: 'Summarize X' });
assert.deepStrictEqual(
{ text: result.text, calls: model.doGenerateCalls.length },
{ text: 'A short summary.', calls: 1 },
);
});
Choose the smallest built-in
| Need |
Use |
| Fixed generation |
new MockLanguageModelV4({ doGenerate: result }) |
| Ordered generations |
doGenerate: [first, second] or doGenerate: mockValues(first, second) |
| Inspect/branch on call options |
function-form doGenerate(options) plus captured call arrays |
| Streaming |
doStream + simulateReadableStream({ chunks, ...delays }) |
| Collect a raw stream |
convertReadableStreamToArray |
| Deterministic generated IDs |
pass generateId: mockId({ prefix: 'test' }) where the public API accepts generateId |
| Test a provider registry |
MockProviderV4 with named V4 models |
| Non-language modality |
the matching V4 mock from the catalog below |
Do not wrap language or embedding mocks merely to record options or maintain a call counter; they already capture calls. The other V4 model mocks expose their operation directly without call arrays, so use Node's mock.fn(...) when the test must inspect those calls. Function-form language-model behavior may inspect model.doGenerateCalls.length; mockValues is better when only the return value changes.
Streaming protocol essentials
- Text and reasoning use
*-start, zero or more *-delta, then *-end, sharing one id.
- Delta payloads use
delta, never textDelta.
- Tool arguments are stringified JSON in the final
tool-call.input.
- A stream ends with
finish, whose finishReason is { unified, raw } and whose usage has the V4 nested token shape.
- Return
{ stream } from doStream; add only typed request or response metadata when the test observes it.
- Use
initialDelayInMs: null and chunkDelayInMs: null for immediate deterministic delivery; use numeric delays only when timing is the behavior under test.
See references/stream-chunks.md for typed V4 chunks and references/recipes.md for full flows.
Use TypeScript's satisfies LanguageModelV4StreamPart[] for compile-time field validation. When debugging chunks extracted as JSON, use scripts/validate-chunks.mjs for lifecycle and ordering checks that the SDK silently ignores. Keep this script: v7 has stream construction and collection helpers, but no public semantic chunk validator.
Complete public test API catalog
Read references/test-api.md when the task is not a basic language-model test or it consumes SDK streams/messages. It covers:
MockLanguageModelV4, MockEmbeddingModelV4, MockImageModelV4
MockSpeechModelV4, MockTranscriptionModelV4, MockRerankingModelV4, MockVideoModelV4
MockProviderV4
mockValues, mockId
convertArrayToAsyncIterable, convertArrayToReadableStream, convertReadableStreamToArray
simulateReadableStream from ai
readUIMessageStream, isTextUIPart, toTextStream, and consumeStream from ai, including their incompatible chunk and error boundaries
timebox from @deepagents/test for status, conversation, and other asynchronous polling
Other references
- references/mock-patterns.md: fixed, sequential, throwing, tool-call, streaming, and provider patterns.
- references/stream-chunks.md: V4 stream part shapes and ordering.
- references/recipes.md: end-to-end retry, tool, structured-output, and stream examples.
- assets/test-template.ts: minimal typed V4 integration-test template.
- scripts/validate-chunks.mjs: V4 JSON chunk lifecycle validator for ordering and matching IDs; use only where static typing cannot help.
When installed types disagree with these files, update the skill to match the installed package rather than adding a compatibility shim.
1---2name: agent-testing3description: Test code built on AI SDK v7 using its real `ai/test` utilities and V4 provider protocol. Use when writing, fixing, reviewing, or migrating tests around AI SDK agents, generation, streaming, structured output, tools, provider registries, model modalities, or simulated streams. Trigger for requests to test an agent, mock a model, fake a stream, simulate a tool call, test retry logic, inspect prompts, or debug hanging streams and empty output. Remove V3 fixtures and custom helpers that duplicate public AI SDK APIs.4---56# Test AI SDK v7 agents78Use AI SDK v7's public test surface as the source of truth. Inspect the installed `ai/test` exports and `@ai-sdk/provider` V4 types before inventing fixtures; v7 changes can make plausible older examples silently wrong.910## Repository test contract1112- Reproduce a bug live against the real API before writing the regression test.13- Prefer integration tests that exercise the complete agent flow.14- Run tests with `nx run <project>:test`; use `node --test --no-warnings path/to/file.test.ts` only for the narrow reproduction or filtered test.15- Run type checks with `nx run <project>:typecheck`, never raw `tsc`.16- Import the package under test by its package specifier, not a relative source path.17- Use `node:assert` and group related observations in `assert.deepStrictEqual` when that makes the failure easier to read.1819## v7-only rules20211. Use V4 mocks (`MockLanguageModelV4`, `MockEmbeddingModelV4`, and the other `*V4` classes). Do not preserve V3 compatibility.222. Import test models and helpers from `ai/test`. Import `simulateReadableStream` from `ai`; its `ai/test` re-export is deprecated.233. Use the built-ins instead of local substitutes:24 - sequential return values: `mockValues(...)`25 - deterministic IDs: `mockId({ prefix })`26 - stream collection: `convertReadableStreamToArray(stream)`27 - stream construction: `convertArrayToReadableStream(values)` or `simulateReadableStream(...)`28 - async iterable construction: `convertArrayToAsyncIterable(values)`29 - language prompt/settings capture: `model.doGenerateCalls` and `model.doStreamCalls`30 - embedding call capture: `model.doEmbedCalls`314. `MockLanguageModelV4` accepts a function, one result, or an array of results for `doGenerate` and `doStream`. Arrays are zero-indexed and return item 0 on the first call.32 A fixed `doStream` result contains one `ReadableStream` and is therefore single-use. Use function form to construct a fresh stream when the same mock can be called more than once, or provide an array containing a distinct stream per expected call.335. Do not add `rawCall`; it is not part of `LanguageModelV4StreamResult`.346. Do not cast fixtures to `any` or `never` to hide protocol errors. Use `satisfies LanguageModelV4GenerateResult`, `LanguageModelV4StreamResult`, or `LanguageModelV4StreamPart[]` so drift fails at compile time.3536## Default language-model fixture3738```ts39import type { LanguageModelV4GenerateResult } from '@ai-sdk/provider';40import { generateText } from 'ai';41import { MockLanguageModelV4 } from 'ai/test';42import assert from 'node:assert';43import { test } from 'node:test';4445const usage = {46 inputTokens: { total: 3, noCache: 3, cacheRead: 0, cacheWrite: 0 },47 outputTokens: { total: 4, text: 4, reasoning: 0 },48} as const;4950const response = {51 content: [{ type: 'text', text: 'A short summary.' }],52 finishReason: { unified: 'stop', raw: 'stop' },53 usage,54 warnings: [],55} satisfies LanguageModelV4GenerateResult;5657test('returns the generated summary', async () => {58 const model = new MockLanguageModelV4({ doGenerate: response });59 const result = await generateText({ model, prompt: 'Summarize X' });6061 assert.deepStrictEqual(62 { text: result.text, calls: model.doGenerateCalls.length },63 { text: 'A short summary.', calls: 1 },64 );65});66```6768## Choose the smallest built-in6970| Need | Use |71| ------------------------------ | --------------------------------------------------------------------------------------- |72| Fixed generation | `new MockLanguageModelV4({ doGenerate: result })` |73| Ordered generations | `doGenerate: [first, second]` or `doGenerate: mockValues(first, second)` |74| Inspect/branch on call options | function-form `doGenerate(options)` plus captured call arrays |75| Streaming | `doStream` + `simulateReadableStream({ chunks, ...delays })` |76| Collect a raw stream | `convertReadableStreamToArray` |77| Deterministic generated IDs | pass `generateId: mockId({ prefix: 'test' })` where the public API accepts `generateId` |78| Test a provider registry | `MockProviderV4` with named V4 models |79| Non-language modality | the matching V4 mock from the catalog below |8081Do not wrap language or embedding mocks merely to record options or maintain a call counter; they already capture calls. The other V4 model mocks expose their operation directly without call arrays, so use Node's `mock.fn(...)` when the test must inspect those calls. Function-form language-model behavior may inspect `model.doGenerateCalls.length`; `mockValues` is better when only the return value changes.8283## Streaming protocol essentials8485- Text and reasoning use `*-start`, zero or more `*-delta`, then `*-end`, sharing one `id`.86- Delta payloads use `delta`, never `textDelta`.87- Tool arguments are stringified JSON in the final `tool-call.input`.88- A stream ends with `finish`, whose `finishReason` is `{ unified, raw }` and whose `usage` has the V4 nested token shape.89- Return `{ stream }` from `doStream`; add only typed `request` or `response` metadata when the test observes it.90- Use `initialDelayInMs: null` and `chunkDelayInMs: null` for immediate deterministic delivery; use numeric delays only when timing is the behavior under test.9192See [references/stream-chunks.md](references/stream-chunks.md) for typed V4 chunks and [references/recipes.md](references/recipes.md) for full flows.9394Use TypeScript's `satisfies LanguageModelV4StreamPart[]` for compile-time field validation. When debugging chunks extracted as JSON, use [scripts/validate-chunks.mjs](scripts/validate-chunks.mjs) for lifecycle and ordering checks that the SDK silently ignores. Keep this script: v7 has stream construction and collection helpers, but no public semantic chunk validator.9596## Complete public test API catalog9798Read [references/test-api.md](references/test-api.md) when the task is not a basic language-model test or it consumes SDK streams/messages. It covers:99100- `MockLanguageModelV4`, `MockEmbeddingModelV4`, `MockImageModelV4`101- `MockSpeechModelV4`, `MockTranscriptionModelV4`, `MockRerankingModelV4`, `MockVideoModelV4`102- `MockProviderV4`103- `mockValues`, `mockId`104- `convertArrayToAsyncIterable`, `convertArrayToReadableStream`, `convertReadableStreamToArray`105- `simulateReadableStream` from `ai`106- `readUIMessageStream`, `isTextUIPart`, `toTextStream`, and `consumeStream` from `ai`, including their incompatible chunk and error boundaries107- `timebox` from `@deepagents/test` for status, conversation, and other asynchronous polling108109## Other references110111- [references/mock-patterns.md](references/mock-patterns.md): fixed, sequential, throwing, tool-call, streaming, and provider patterns.112- [references/stream-chunks.md](references/stream-chunks.md): V4 stream part shapes and ordering.113- [references/recipes.md](references/recipes.md): end-to-end retry, tool, structured-output, and stream examples.114- [assets/test-template.ts](assets/test-template.ts): minimal typed V4 integration-test template.115- [scripts/validate-chunks.mjs](scripts/validate-chunks.mjs): V4 JSON chunk lifecycle validator for ordering and matching IDs; use only where static typing cannot help.116117When installed types disagree with these files, update the skill to match the installed package rather than adding a compatibility shim.