BDD Test File Generator
Generate behavior-driven test files that focus on public API and observable behavior.
Quick Start Workflow
- Read the source file to understand what needs to be tested
- Identify the public API - all exported functions, constants, hooks, or component props
- Check for existing test setup - look for
setup.ts, vitest.config.ts, or existing test files
- Ask the user: unit, integration, or both? (see below)
- Delegate context + edge-case analysis to an agent (see below) — do
not try to produce the full case list from the source file alone
- Review the agent's list with the user, prune/add cases, then
generate the test file using patterns from
references/patterns.md
Delegate Analysis to a Subagent
A single file rarely tells the full story: callers pass specific shapes,
sibling files encode invariants, existing tests hint at conventions, and the
bug that prompted the test may live in the git log. Before writing cases,
spawn a subagent to gather that context and draft the case list.
Use the Explore agent (or general-purpose if deeper reasoning is needed)
via the Agent tool. Give it a self-contained brief that includes:
- The exact source path(s) being tested
- The testing level chosen in the previous step (unit / integration / both)
- The framework stack (e.g. vitest + testing-library, jest + supertest)
- The edge-case dimensions listed under Edge Cases: Analyze from Context
— ask the agent to walk those dimensions against the actual code, not as
a generic checklist
- An instruction to read callers, sibling modules, existing tests, and any
obvious schema/constant files before proposing cases
Ask the agent to report back using a BDD-native shape — the same
describe / context / it nesting with GIVEN/WHEN/THEN that the generated
test file will use. This keeps review cheap (the scenarios map 1:1 to the
code you're about to write) and keeps the skill internally consistent.
Prefer spec style (matches vitest / jest / RSpec output):
describe <unit under test>
context <condition, state, or collaborator setup>
it <expected observable behavior>
GIVEN ...
WHEN ...
THEN ...
[src/foo.ts:42 — `if (!user)` branch]
it ...
context ...
Open questions
- <ambiguity the agent couldn't resolve from the code>
Use Gherkin-style Feature / Scenario instead only if the project already
uses Cucumber or a Gherkin runner.
Notes for the agent's report:
- Group scenarios by the code branch or collaborator state they exercise
(the
context), not by a "happy / edge / error" bucket — BDD treats them
as a single flat list of scenarios.
- Annotate each
it with the code anchor that motivates it, so pruning is
reviewable. Prefer a stable anchor — a named branch or symbol
(`if (!user)` branch, split('?')) — over file:line; line
numbers drift even between the agent writing the report and you reading
it. This annotation lives in the report, to be reviewed and discarded.
- Cap report length (e.g. "under 400 lines") so it stays reviewable.
When the report comes back:
- Resolve the Open Questions with the user before generating code
- Drop any case the code doesn't actually distinguish
- Add anything the agent missed that you can justify from the source
Only then move on to generating the test file.
Ask: Unit, Integration, or Both?
Before generating anything, ask the user which level(s) of tests they want —
don't assume. Use AskUserQuestion (or a plain question if that tool isn't
available) with these choices, explained in terms of this file:
- Unit tests — exercise the module in isolation; collaborators (network,
DB, other modules, timers, the DOM beyond what a single hook/component
needs) are mocked or stubbed. Fast, many cases, focused on one unit's
contract.
- Integration tests — let the real collaborators run and test how this
module behaves inside its actual neighborhood (real DB/driver, real HTTP
client hitting a test server, real router, multiple hooks/components
composed together). Slower, fewer cases, focused on wiring and
boundaries.
- Both — produce separate files (e.g.
foo.test.ts and
foo.integration.test.ts) so they can run under different configs.
Recommend a default based on the file:
| File kind |
Default recommendation |
| Pure function / utility / constants |
Unit |
| Hook / component with mockable deps |
Unit |
| Repository / DB query / HTTP client |
Integration |
| Router, workflow, or multi-module orchestrator |
Both |
State the recommendation and why, but defer to the user's answer.
Edge Cases: Analyze from Context, Don't Use a Generic Checklist
"Empty string / null / zero" is a starting list, not the finish line. Before
writing cases, read the source carefully and derive edge cases from what the
code actually does. Check each of these against the file in front of you:
- Inputs and types — what does each parameter accept? For every type,
what are its degenerate values (empty, zero-length,
undefined, NaN,
-0, very large, very small, unicode, trailing whitespace)?
- Branches and guards — every
if, switch, ?., ??, try/catch,
early return. Each branch is an edge case worth naming.
- Boundaries — off-by-one around lengths/indices, inclusive vs.
exclusive ranges, min/max of numeric domains, first/last element
behavior.
- State and time — initial render vs. after update, before vs. after
async resolution, stale closures, cleanup on unmount, race between two
in-flight requests, timers firing after teardown.
- Collaborator failures — what happens when the thing this code calls
throws, times out, returns
null, or returns an unexpected shape? Cover
the ones the code handles, and at least one it doesn't (to document
the contract).
- Concurrency & ordering — duplicate events, rapid re-renders,
out-of-order responses, double-submits.
- Authorization & identity — missing user, wrong role, expired token —
wherever the code branches on identity.
- Environment — feature-flag on/off, locale/timezone, SSR vs. client,
dev vs. prod env checks in the code.
Only include cases that the code's behavior actually distinguishes. Don't
pad the file with cases the implementation treats identically — one test per
observable behavior.
Core Principle
Test public API and observable behavior only, never internal implementation:
- Hooks/Components: Test user interactions, props, rendered output
- Functions/Utilities: Test inputs → outputs, not internal algorithm steps
- Constants: Test exported values are correct
Test Description Style
Use BDD-style descriptions with flexible GIVEN/WHEN/THEN/AND comments:
// Full form
// GIVEN an unauthenticated user
// WHEN the protected route is accessed
// THEN the user should be redirected
// With AND for multiple assertions
// GIVEN a user is authenticated
// WHEN the profile page loads
// THEN the username should be displayed
// AND the avatar should be visible
// Simple form (no WHEN needed)
// GIVEN an empty array
// THEN length should be zero
// Multiple conditions
// GIVEN a valid token
// AND the user has admin role
// WHEN accessing admin panel
// THEN access should be granted
Optional code anchor in the committed test
The motivating annotations from the agent's case-list report are for review
and pruning — by default keep them OUT of the committed test; the
GIVEN/WHEN/THEN already documents intent. Add an anchor only when an
assertion genuinely benefits from pointing at the code it locks in (e.g.
several sibling its each pin a different branch). When you do, use a
bare symbol or branch hint in brackets and nothing more:
// GIVEN a magic-link URL with the token in the query
// WHEN normalised
// THEN only the path survives
// [split('?')]
Never put a file:line in a committed test — the line goes stale on the
next edit above it and silently misleads. Drop the filename too when the
test is co-located with the unit it imports; it's redundant.
Coverage Requirements
- Primary success paths (happy path)
- Edge cases derived from the code (see Edge Cases: Analyze from Context
above — not a generic "empty / null / zero" checklist)
- Error states (graceful error handling for every
catch, rejection, or
fallback branch in the source)
- All exported items
- For integration tests: the real wiring between this module and each
collaborator it owns in production (don't re-test the collaborator itself)
References
references/patterns.md - Detailed test patterns and mock handling
references/examples.md - Complete example test files
1---2name: test-bdd3description: Generate BDD-style test files that document behavior with GIVEN/WHEN/THEN comments and test only public API and observable outcomes. Language and framework agnostic, with patterns and examples tuned for TypeScript + vitest + testing-library (hooks, components, utilities, constants).4license: MIT5---67# BDD Test File Generator89Generate behavior-driven test files that focus on public API and observable behavior.1011## Quick Start Workflow12131. **Read the source file** to understand what needs to be tested141. **Identify the public API** - all exported functions, constants, hooks, or component props151. **Check for existing test setup** - look for `setup.ts`, `vitest.config.ts`, or existing test files161. **Ask the user: unit, integration, or both?** (see below)171. **Delegate context + edge-case analysis to an agent** (see below) — do18 not try to produce the full case list from the source file alone191. **Review the agent's list with the user**, prune/add cases, then20 generate the test file using patterns from `references/patterns.md`2122## Delegate Analysis to a Subagent2324A single file rarely tells the full story: callers pass specific shapes,25sibling files encode invariants, existing tests hint at conventions, and the26bug that prompted the test may live in the git log. Before writing cases,27spawn a subagent to gather that context and draft the case list.2829Use the `Explore` agent (or `general-purpose` if deeper reasoning is needed)30via the `Agent` tool. Give it a self-contained brief that includes:3132- The exact source path(s) being tested33- The testing level chosen in the previous step (unit / integration / both)34- The framework stack (e.g. vitest + testing-library, jest + supertest)35- The edge-case dimensions listed under **Edge Cases: Analyze from Context**36 — ask the agent to walk those dimensions against the actual code, not as37 a generic checklist38- An instruction to read callers, sibling modules, existing tests, and any39 obvious schema/constant files before proposing cases4041Ask the agent to report back using a BDD-native shape — the same42`describe / context / it` nesting with `GIVEN/WHEN/THEN` that the generated43test file will use. This keeps review cheap (the scenarios map 1:1 to the44code you're about to write) and keeps the skill internally consistent.4546Prefer spec style (matches vitest / jest / RSpec output):4748```text49describe <unit under test>50 context <condition, state, or collaborator setup>51 it <expected observable behavior>52 GIVEN ...53 WHEN ...54 THEN ...55 [src/foo.ts:42 — `if (!user)` branch]56 it ...57 context ...5859Open questions60 - <ambiguity the agent couldn't resolve from the code>61```6263Use Gherkin-style `Feature / Scenario` instead only if the project already64uses Cucumber or a Gherkin runner.6566Notes for the agent's report:6768- Group scenarios by the code branch or collaborator state they exercise69 (the `context`), not by a "happy / edge / error" bucket — BDD treats them70 as a single flat list of scenarios.71- Annotate each `it` with the code anchor that motivates it, so pruning is72 reviewable. Prefer a **stable** anchor — a named branch or symbol73 (`` `if (!user)` branch ``, `split('?')`) — over `file:line`; line74 numbers drift even between the agent writing the report and you reading75 it. This annotation lives in the report, to be reviewed and discarded.76- Cap report length (e.g. "under 400 lines") so it stays reviewable.7778When the report comes back:79801. Resolve the **Open Questions** with the user before generating code811. Drop any case the code doesn't actually distinguish821. Add anything the agent missed that you can justify from the source8384Only then move on to generating the test file.8586## Ask: Unit, Integration, or Both?8788Before generating anything, ask the user which level(s) of tests they want —89don't assume. Use `AskUserQuestion` (or a plain question if that tool isn't90available) with these choices, explained in terms of *this* file:9192- **Unit tests** — exercise the module in isolation; collaborators (network,93 DB, other modules, timers, the DOM beyond what a single hook/component94 needs) are mocked or stubbed. Fast, many cases, focused on one unit's95 contract.96- **Integration tests** — let the real collaborators run and test how this97 module behaves inside its actual neighborhood (real DB/driver, real HTTP98 client hitting a test server, real router, multiple hooks/components99 composed together). Slower, fewer cases, focused on wiring and100 boundaries.101- **Both** — produce separate files (e.g. `foo.test.ts` and102 `foo.integration.test.ts`) so they can run under different configs.103104Recommend a default based on the file:105106| File kind | Default recommendation |107| ---------------------------------------------- | ---------------------- |108| Pure function / utility / constants | Unit |109| Hook / component with mockable deps | Unit |110| Repository / DB query / HTTP client | Integration |111| Router, workflow, or multi-module orchestrator | Both |112113State the recommendation and why, but defer to the user's answer.114115## Edge Cases: Analyze from Context, Don't Use a Generic Checklist116117"Empty string / null / zero" is a starting list, not the finish line. Before118writing cases, read the source carefully and derive edge cases from what the119code actually does. Check each of these against the file in front of you:120121- **Inputs and types** — what does each parameter accept? For every type,122 what are its degenerate values (empty, zero-length, `undefined`, `NaN`,123 `-0`, very large, very small, unicode, trailing whitespace)?124- **Branches and guards** — every `if`, `switch`, `?.`, `??`, `try/catch`,125 early return. Each branch is an edge case worth naming.126- **Boundaries** — off-by-one around lengths/indices, inclusive vs.127 exclusive ranges, min/max of numeric domains, first/last element128 behavior.129- **State and time** — initial render vs. after update, before vs. after130 async resolution, stale closures, cleanup on unmount, race between two131 in-flight requests, timers firing after teardown.132- **Collaborator failures** — what happens when the thing this code calls133 throws, times out, returns `null`, or returns an unexpected shape? Cover134 the ones the code *handles*, and at least one it *doesn't* (to document135 the contract).136- **Concurrency & ordering** — duplicate events, rapid re-renders,137 out-of-order responses, double-submits.138- **Authorization & identity** — missing user, wrong role, expired token —139 wherever the code branches on identity.140- **Environment** — feature-flag on/off, locale/timezone, SSR vs. client,141 dev vs. prod env checks in the code.142143Only include cases that the code's behavior actually distinguishes. Don't144pad the file with cases the implementation treats identically — one test per145observable behavior.146147## Core Principle148149Test public API and observable behavior only, never internal implementation:150151- **Hooks/Components**: Test user interactions, props, rendered output152- **Functions/Utilities**: Test inputs → outputs, not internal algorithm steps153- **Constants**: Test exported values are correct154155## Test Description Style156157Use BDD-style descriptions with flexible GIVEN/WHEN/THEN/AND comments:158159```typescript160// Full form161// GIVEN an unauthenticated user162// WHEN the protected route is accessed163// THEN the user should be redirected164165// With AND for multiple assertions166// GIVEN a user is authenticated167// WHEN the profile page loads168// THEN the username should be displayed169// AND the avatar should be visible170171// Simple form (no WHEN needed)172// GIVEN an empty array173// THEN length should be zero174175// Multiple conditions176// GIVEN a valid token177// AND the user has admin role178// WHEN accessing admin panel179// THEN access should be granted180```181182### Optional code anchor in the committed test183184The motivating annotations from the agent's case-list report are for review185and pruning — by default keep them OUT of the committed test; the186GIVEN/WHEN/THEN already documents intent. Add an anchor only when an187assertion genuinely benefits from pointing at the code it locks in (e.g.188several sibling `it`s each pin a different branch). When you do, use a189**bare symbol or branch hint** in brackets and nothing more:190191```typescript192// GIVEN a magic-link URL with the token in the query193// WHEN normalised194// THEN only the path survives195// [split('?')]196```197198Never put a `file:line` in a committed test — the line goes stale on the199next edit above it and silently misleads. Drop the filename too when the200test is co-located with the unit it imports; it's redundant.201202## Coverage Requirements203204- Primary success paths (happy path)205- Edge cases derived from the code (see **Edge Cases: Analyze from Context**206 above — not a generic "empty / null / zero" checklist)207- Error states (graceful error handling for every `catch`, rejection, or208 fallback branch in the source)209- All exported items210- For integration tests: the real wiring between this module and each211 collaborator it owns in production (don't re-test the collaborator itself)212213## References214215- `references/patterns.md` - Detailed test patterns and mock handling216- `references/examples.md` - Complete example test files