Testing Flows
How to write deterministic tests for your flows and blocks using @flow-state-dev/testing.
Setup
pnpm add -D @flow-state-dev/testing vitest
Testing Blocks
Handler
import { handler } from "@flow-state-dev/core";
import { testBlock } from "@flow-state-dev/testing";
import { z } from "zod";
const validator = handler({
name: "validator",
inputSchema: z.object({ email: z.string() }),
outputSchema: z.object({ valid: z.boolean() }),
execute: async (input) => ({
valid: input.email.includes("@"),
}),
});
test("validates email format", async () => {
const result = await testBlock(validator, {
input: { email: "[email protected]" },
});
expect(result.output.valid).toBe(true);
});
Generator (with mocks)
Generators call LLMs, so tests use scripted mocks:
import { testBlock } from "@flow-state-dev/testing";
test("chat generator produces response", async () => {
const result = await testBlock(chatGen, {
input: { message: "Hello" },
generators: {
"chat": { output: "Hi there!" },
},
});
expect(result.output).toBe("Hi there!");
});
Sequencer
import { testSequencer } from "@flow-state-dev/testing";
test("pipeline processes message", async () => {
const result = await testSequencer(pipeline, {
input: { message: "Hello" },
session: { state: { messageCount: 0 } },
generators: {
"chat": { output: "Hi!" },
},
});
expect(result.session.state.messageCount).toBe(1);
});
Testing Flows (End-to-End)
testFlow tests the full action execution path:
import { testFlow } from "@flow-state-dev/testing";
import myFlow from "../flow";
test("chat action works end-to-end", async () => {
const result = await testFlow({
flow: myFlow,
action: "chat",
input: { message: "What is AI?" },
userId: "testuser",
generators: {
"chat": { output: "AI is artificial intelligence." },
},
});
// Check emitted items
expect(result.items).toContainEqual(
expect.objectContaining({ type: "message", role: "user" })
);
// Check final state
expect(result.session.state.messageCount).toBe(1);
});
Seeding State
Pre-populate scope state and resources:
const result = await testFlow({
flow: myFlow,
action: "run",
input: { prompt: "Continue" },
userId: "testuser",
seed: {
session: {
state: { mode: "agent", step: 3 },
resources: {
plan: { steps: ["step1", "step2"], status: "active" },
},
},
user: {
state: { preferredModel: "gpt-4o" },
},
},
generators: {
"agent": { output: { action: "complete" } },
},
});
Mock Generator Options
Simple output mock
generators: {
"chat": { output: "Hello!" },
}
Mock with items
generators: {
"chat": {
output: "Hello!",
items: [
{
type: "message",
role: "assistant",
content: [{ type: "text", text: "Hello!" }],
},
],
},
}
Mock by model ID (fallback)
models: {
"gpt-5-mini": { output: "Default response" },
}
Generator mocks are resolved by block name first (generators), then model ID (models).
Item Assertions
import { testItems } from "@flow-state-dev/testing";
const items = testItems(result.items);
expect(items.messages()).toHaveLength(2); // user + assistant
expect(items.blockOutputs()).toHaveLength(1);
expect(items.ofType("tool_call")).toHaveLength(0);
Snapshot Traces
For debugging complex pipelines:
import { snapshotTrace } from "@flow-state-dev/testing";
const trace = snapshotTrace(result);
// Returns a summary of steps, items, and state changes