Vercel AI SDKAI SDK 7 · TypeScript · Node.js 20+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
17 small wins to finish your pathNext lesson

Testing with node:test and mock models

Unit tests replace the model with MockLanguageModelV4 answers you choose, then check what your code does with them and what it sent to the model.

Exampleticket.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { generateText, Output } from "ai";
import { MockLanguageModelV4 } from "ai/test";
import { Ticket } from "./ticket.ts";

const usage = {
  inputTokens: { total: 1, noCache: 1, cacheRead: undefined, cacheWrite: undefined },
  outputTokens: { total: 1, text: 1, reasoning: undefined },
};
const answer = (text: string) => new MockLanguageModelV4({
  doGenerate: async () => ({ content: [{ type: "text", text }], finishReason: { unified: "stop", raw: undefined }, usage, warnings: [] }),
});

test("a valid ticket is parsed", async () => {
  const { output } = await generateText({ model: answer('{"category":"billing","priority":5}'), output: Output.object({ schema: Ticket }), prompt: "x" });
  assert.deepEqual(output, { category: "billing", priority: 5 });
});

test("a priority of 9 is rejected", async () => {
  const run = generateText({ model: answer('{"category":"billing","priority":9}'), output: Output.object({ schema: Ticket }), prompt: "x" });
  await assert.rejects(run, { name: "AI_NoObjectGeneratedError" });
});

test("the system prompt reaches the model", async () => {
  const model = answer("ok");
  await generateText({ model, system: "Be kind.", prompt: "Hi" });
  assert.deepEqual(model.doGenerateCalls[0].prompt[0], { role: "system", content: "Be kind." });
});
Example
npx tsx --test ticket.test.ts

Node's built-in test runner, run through tsx with --test. answer builds a mock that returns fixed text, so each test controls the model exactly:

  • A valid answer parses into the typed object.
  • An invalid one, priority 9, rejects with AI_NoObjectGeneratedError: your schema's limits are tested without a model misbehaving on cue.
  • doGenerateCalls proves the system prompt was sent first.

The same pattern works with Vitest or Jest. simulateReadableStream and doStream test streaming code, as shop-model.ts does.

Try it yourself
  • Test that a category of refunds is rejected.
  • Test the tools.ts tool's execute directly.
  • Use mockValues from ai/test to answer differently on each call.

Every expert started right here.