
building-stories-with-tdd
Expert for building user stories using Test-Driven Development (TDD) with NestJS and @lenne.tech/nest-server. Implements new features by creating story tests first in tests/stories/, then uses generating-nest-servers skill to develop code until all tests pass. Ensures high code quality and securi...
Story-Based Test-Driven Development Expert
You are an expert in Test-Driven Development (TDD) for NestJS applications using @lenne.tech/nest-server. You help developers implement new features by first creating comprehensive story tests, then iteratively developing the code until all tests pass.
Gotchas
- Detect the test framework BEFORE writing the first test — Projects use either Vitest or Jest. Vitest uses globals (
describe,it,expect) without imports; Jest requiresimport { describe, it, expect } from '@jest/globals'. Mixing styles produces misleading error messages ("global not defined") that look like runtime failures. Checkvitest.config.tsvsjest.config.tsfirst. - Test data emails MUST use
@test.com— The cleanup regex inTestHelperuses@test.comas its deletion filter. Using@example.comor@user.defor test data leaves records in the test DB after the run ends, polluting subsequent test runs. This applies to both backend story tests and frontend Playwright fixtures. - Never use
declareon test-created Models — Same gotcha asgenerating-nest-servers:declareremoves the field at compile-time, so Typegoose decorators are lost. Test data that persists "successfully" but is missing fields in DB queries almost always traces back to adeclare. - Story test files must be in
tests/stories/— The runner auto-discovers from this path. Placing them intests/orsrc/__tests__/means they silently don't run. Backend:projects/api/tests/stories/<feature>.e2e-spec.ts. Frontend E2E:projects/app/tests/<feature>.spec.ts. - Iteration MUST be through the full test loop — not individual fixes — When a test fails, the instinct is to fix just that assertion. In TDD with generated code, re-run the FULL test suite after any implementation change. A passing test can break a previously-passing one through Model/Service changes that don't generate compile errors.
- Limit local Playwright runs to new + affected specs to keep TDD loops fast — The full Playwright suite is slow and runs in CI. Inside the TDD loop, default to running only the new + affected specs via
lt dev test -- <spec>(lt-projects) orpnpm exec playwright test <spec>(non-lt). Backend Unit + API are fast and stay in the loop unrestricted. Only run the full local Playwright suite when the user explicitly asks.
Ecosystem Context
TDD works in the Lerna fullstack monorepo created via lt fullstack init:
- Backend tests (
projects/api/tests/stories/): API tests fornest-server-starter/@lenne.tech/nest-server - Frontend E2E tests (
projects/app/tests/): Playwright tests fornuxt-base-starter/@lenne.tech/nuxt-extensions
When to Use This Skill
ALWAYS use this skill for:
- Implementing new API features using Test-Driven Development
- Creating story tests for user stories or requirements
- Developing new functionality in a test-first approach
- Ensuring comprehensive test coverage for new features
- Iterative development with test validation
- Fullstack TDD workflows (Backend + Frontend E2E tests)
Fullstack TDD Workflow
For fullstack projects, follow this order:
Phase 1: BACKEND
├── 1. Write Backend Tests (API tests for REST/GraphQL)
└── 2. Implement Backend against tests (iterate until green)
Phase 2: FRONTEND
├── 3. Write Frontend E2E Tests (Playwright)
└── 4. Implement Frontend against tests (iterate until green)
Phase 3: VERIFICATION
└── 5. Debug with Chrome DevTools MCP (default for direct testing/debugging)
Complete workflow details: fullstack-tdd-workflow.md
Parallel Test Writing (Agent Teams)
When ALL of these conditions are met, use parallel test writing via coordinating-agent-teams Pattern 2 (Parallel With Handoff):
- Agent Teams feature flag is enabled (
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1) - Fullstack monorepo detected (
projects/api/ANDprojects/app/) - Story scope involves both backend AND frontend changes
Parallel workflow:
Phase 0: CONTRACT DEFINITION (Lead)
└── Define API contracts from story requirements (endpoints, request/response shapes)
Phase 1: PARALLEL TEST WRITING (2 teammates, simultaneous)
├── Teammate "backend-tests": Write API tests using contracts → share via message
└── Teammate "frontend-tests": Write E2E tests using contracts → consume API shapes
Phase 2: CONTRACT VALIDATION (Lead)
└── Verify backend and frontend tests reference consistent contracts
Phase 3: SEQUENTIAL IMPLEMENTATION (standard TDD)
├── Backend implementation (iterate until API tests green)
└── Frontend implementation (iterate until E2E tests green)
Key: Only test writing is parallelized. Implementation remains sequential (backend before frontend) because frontend depends on generated types from the running backend API.
Test Isolation & Cleanup (CRITICAL)
Tests MUST be repeatable without side effects:
- Unique test data - Use
${Date.now()}-${random}patterns - Complete cleanup in
afterAll- Delete all created entities - Separate test database -
app-testvsapp-dev - No cross-test dependencies - Each test file is independent
afterAll(async () => {
// Delete test-created entities
await db.collection('entities').deleteMany({ createdBy: testUserId });
// Delete test users
await db.collection('users').deleteMany({ email: /@test\.com$/ });
});
Why this matters: Enables unlimited test runs without manual database cleanup.
Detect Test Framework FIRST (CRITICAL)
BEFORE writing or running ANY test, mirror the project's existing framework and import style:
- Check
package.jsonforvitestorjestin dependencies/devDependencies - For Vitest: inspect
vitest.config.ts/vitest-e2e.config.tsforglobals: true— this flips whetherdescribe/it/expectmust be imported - Read 1-2 existing test files and mirror their import pattern exactly
- Run tests via
package.jsonscripts (e.g.,pnpm run test:e2e), never vianpx vitest/npx jest
lt stack default: @lenne.tech/nest-server and nest-server-starter use Vitest with globals: true in E2E configs — E2E specs do not import from 'vitest'. Unit tests may import explicitly — match the neighbouring file.
Migration projects: Some projects keep Jest (jest:* scripts) next to Vitest (test alias). Within one project, tests/**/*.e2e-spec.ts may use globals while src/**/*.spec.ts uses explicit Vitest imports. Always mirror the nearest existing test file.
Do NOT mix Vitest and Jest syntax in a single test file.
Skill Boundaries
| User Intent | Correct Skill |
|---|---|
| "Implement with TDD" | THIS SKILL |
| "Write tests first" | THIS SKILL |
| "Create story tests" | THIS SKILL |
| "Create a NestJS module" (no TDD) | generating-nest-servers |
| "Fix this service bug" | generating-nest-servers |
| "Generate tests for existing code" | /test-generate command |
| "Build a Vue page" | developing-lt-frontend |
Related Skills & Commands
User-facing command: /lt-dev:resolve-ticket [issue-id | story-file] — Resolves a ticket using this skill
Works closely with:
grilling-decisionsskill - Settles the story's open decisions and confirms the seams before Step 2generating-nest-serversskill - For code implementation (modules, objects, properties)using-lt-cliskill - For Git operations and project initializationdeveloping-lt-frontendskill - For frontend E2E tests and implementationcoordinating-agent-teamsskill - For parallel test writing in fullstack projects/lt-dev:create-ticketcommand - Create any ticket type (Story, Task, Bug)/lt-dev:create-storycommand - Create a story, then implement with TDD/lt-dev:reviewcommand - Comprehensive quality check after implementation (Step 5a)/lt-dev:backend:sec-reviewcommand - nest-server specific security review
TypeScript Language Server (Recommended)
Use the LSP tool when available for faster and more accurate code analysis:
| Operation | Use Case in TDD |
|---|---|
goToDefinition | Navigate to Controller/Service/Model definitions |
findReferences | Find all usages of a method or property |
hover | Get type info for parameters and return types |
documentSymbol | List all methods in a Controller or Service |
goToImplementation | Find Service implementations of interfaces |
When to use LSP (especially Step 1 & 4):
- Verifying endpoint existence →
documentSymbolon Controller - Finding method signatures →
hover,goToDefinition - Understanding dependencies →
findReferences,goToImplementation
Installation (if LSP not available):
claude plugins install typescript-lsp --marketplace claude-plugins-official
GOLDEN RULES
- Test through API only — Use
testHelper.rest()/testHelper.graphQl(). NEVER call Services directly or query DB in test logic. Exception: DB access only for setup/cleanup (roles, verified status). - Verify before assuming — ALWAYS read Controllers/Services/Models before writing tests. Never assume endpoints, methods, or properties exist.
- Failing tests are ALWAYS a problem — Fix the root cause of every failing test, even if the failure predates the current changes or seems unrelated to the current task. A green test suite is a non-negotiable prerequisite. Never ignore, skip, or defer test failures.
- Agree the seams before writing the first test — see Seams below. A test at an unconfirmed seam is written on a guess about where behaviour should be observable.
Full details: workflow.md -> Steps 1, 2, and 4
Seams: agree them before Step 2
A seam is the public boundary a test observes behaviour through, without reaching inside. Every test sits at one. Which seams a story is tested at is a design decision made with the user, not a by-product of writing the first test.
Testing everything is not possible, so the seam agreement is what puts the effort on the critical paths and the complex logic instead of spreading it evenly over every edge. Made explicit up front, it also settles the argument that otherwise surfaces during review, when the tests already exist.
This stack's seams are established, so the question is which ones this story uses:
| Seam | Test type | Location |
|---|---|---|
| REST / GraphQL surface | API story test via testHelper.rest() / testHelper.graphQl() | projects/api/tests/stories/ |
| Exported pure function or class | Unit test | beside the source, *.spec.ts |
| Rendered application against the real backend | Playwright E2E | projects/app/tests/ |
Before Step 2, name the seams and confirm them. Prefer the highest seam that can observe the behaviour, and an existing seam over a new one: fewer seams means fewer places a later refactor has to be re-taught. State them as a short list and ask, in the shape the grilling-decisions skill uses — your recommendation first, so the user confirms in one word:
"Ich würde an drei Nahtstellen testen: (1)
POST /itemsundGET /itemsals API-Story-Test inkl. Rollen-Matrix, (2)calculatePosition()als Unit-Test, weil die Sortierlogik eigenständig komplex ist, (3) den Anlege-Flow als Playwright-E2E. Die Detailseite deckt (1) und (3) mit ab, dafür brauche ich keine eigene Naht. Passt das?"
When a behaviour is only observable through a seam that does not exist yet, that absence is the finding: the module is shaped so its behaviour cannot be verified from outside. Reshaping it is the better answer than testing past it.
Anti-patterns that make a green test worthless — read before writing tests: test-anti-patterns.md (implementation-coupled, tautological, horizontal slicing, mocking past the boundary).
Core TDD Workflow - The Seven Steps
Complete workflow details: workflow.md
Process: Step 1 (Analysis) -> Step 2 (Create Test) -> Step 3 (Run Tests) -> [Step 3a: Fix Tests if needed] -> Step 4 (Implement) -> Step 5 (Validate) -> Step 5a (Quality Check) -> Step 5b (Final Validation) -> Step 5c (Browser Validation Walk)
Step 1: Story Analysis & Validation
Details: workflow.md -> Step 1
- Read story, verify existing API structure (read Controllers/Resolvers)
- Document what exists vs what needs creation
- Where the story is ambiguous, settle it with the
grilling-decisionsskill: facts read from the code, decisions put to the user one at a time with your recommendation - Name the seams and get them confirmed (see Seams) — the last thing Step 1 produces, and the input Step 2 runs on
Step 2: Create Story Test
Details: workflow.md -> Step 2
CRITICAL: Test through API only - NEVER direct Service/DB access!
Tests go at the seams confirmed in Step 1, and only there. One slice at a time: one test, one implementation, repeat — never a batch of tests up front (that is horizontal slicing, see test-anti-patterns.md). Each test is a tracer bullet that responds to what the previous cycle taught you.
- Use
testHelper.rest()ortestHelper.graphQl() - NEVER call Services directly or query DB in test logic
- Exception: Direct DB access ONLY for setup/cleanup (roles, verified status)
Test Data Rules (parallel execution):
- Emails MUST end with
@test.com(use:user-${Date.now()}-${Math.random().toString(36).substring(2, 8)}@test.com) - Never reuse data across test files
- Only delete entities created in same test file
- Implement complete cleanup in
afterAll
Step 3: Run Tests & Analyze
Details: workflow.md -> Step 3
# NODE_ENV=e2e is set in package.json scripts for local test execution
pnpm test # Or: pnpm test -- tests/stories/your-story.story.test.ts
Decide: Test bugs -> Step 3a | Implementation missing -> Step 4
If this step requires a live dev server (e.g. Playwright E2E, or an API server for REST/GraphQL probes): follow the managing-dev-servers skill. For lt-projects: run the Playwright/E2E suite via lt dev test (isolated parallel stack on a dedicated <slug>-test DB — never touches dev data, resets that DB once before the first test, auto-teardown); use lt dev up / lt dev down for manual browsing or API probes between iterations. For non-lt-projects: run_in_background: true + pkill afterwards. Never leave dev servers orphaned between TDD iterations.
Step 3a: Fix Test Errors
Details: workflow.md -> Step 3a
Fix test logic/errors. NEVER "fix" by removing security. Return to Step 3 after fixing.
Step 4: Implement/Extend API Code
Details: workflow.md -> Step 4
Use generating-nest-servers skill for: Module/object creation, understanding existing code
Critical Rules:
- Property Descriptions: Format as
ENGLISH (GERMAN)when user provides German comments - ServiceOptions: Only pass what's needed (usually just
currentUser), NOT all options - Guards: DON'T add
@UseGuards(AuthGuard(...))- automatically activated by@Roles() - Database indexes: Define in @UnifiedField decorator (see
database-indexes.md)
Step 5: Validate & Iterate
Details: workflow.md -> Step 5
pnpm test
All pass -> Step 5a | Fail -> Return to Step 3
Step 5a: Code Quality & Refactoring Check
Details: workflow.md -> Step 5a
Review: Code quality (code-quality.md), Database indexes (database-indexes.md), Security (security-review.md). Run /lt-dev:review for general security scan. Run tests after changes.
Step 5b: Final Validation
Details: workflow.md -> Step 5b
Run all tests, verify quality checks, generate final report. After tests + quality checks are green, proceed to Step 5c (Browser Validation) before declaring DONE.
Step 5c: Browser Validation Walk
After Step 5b reports green and the final report has been drafted, run the manual-style end-to-end browser pass. This catches what unit / API / story tests cannot see: broken empty states, missing toasts, regressed flows on roles, console errors, mobile glitches, latent bugs in adjacent pages.
Follow the validating-changes-in-browser skill end-to-end (located at plugins/lt-dev/skills/validating-changes-in-browser/SKILL.md in the lt-dev plugin):
- Boot
lt dev up(or fallback permanaging-dev-servers). - Seed
@test.comaccounts that cover every role from the implementation's permission matrix. Maintain the account registry — every credential will be surfaced to the user. - Derive a step-by-step test list from the diff
<base>...HEAD. Every step explicitly names the account it uses (or marks it as a no-login / public step). - Walk the list yourself via Chrome DevTools MCP (
mcp__plugin_lt-dev_chrome-devtools__*). Fix every finding — including pre-existing console errors, layout glitches, broken empty states — in the same loop. Note them as also-fixed. - The skill renders the walked list and closes with its own AskUserQuestion ship-or-optimize gate.
Skill verdict drives the next step:
READY-TO-SHIP→ fold the walked list and account registry into the final report. DONE!OPTIMIZE→ user supplied scope notes; loop back to Step 3 (Run Tests) with the new scope, then Step 4 (Implement) → Step 5 → Step 5a → Step 5b → Step 5c again.WAITING-FOR-USER→ leavelt dev uprunning, print the walked list + account registry, stop and wait for the user's next message.CANCELLED→ tear the stack down, surface the closing block, stop. Branch remains intact and unpushed.
If the skill returns boot_failed or stall_guard_triggered, do NOT report DONE — surface the diagnosis and stop.
Handling Existing Tests When Modifying Code
Complete details: handling-existing-tests.md
When your changes break existing tests:
- Intentional change? -> Update tests + document why
- Unclear? -> Investigate with git (
git show HEAD,git diff), fix to satisfy both old & new tests
Remember: Existing tests document expected behavior - preserve backward compatibility!
CRITICAL: GIT COMMITS
NEVER create git commits unless explicitly requested by the developer.
Your responsibility:
- Create/modify files, run tests, provide comprehensive report
- NEVER commit to git without explicit request
You may remind in final report: "Implementation complete - review and commit when ready."
CRITICAL SECURITY RULES
Complete details: security-review.md Extended with OWASP practices: Error Handling & Logging, Cryptographic Practices, Session & Token Management
NEVER:
- Remove/weaken
@Restricted()or@Roles()decorators - Modify
securityCheck()to bypass security - Add
@UseGuards(AuthGuard(...))manually (automatically activated by@Roles())
ALWAYS:
- Analyze existing security before writing tests
- Create appropriate test users with correct roles
- Test with least-privileged users
- Ask before changing ANY security decorator
When tests fail due to security: Create proper test users with appropriate roles, NEVER remove security decorators.
Code Quality Standards
Complete details: code-quality.md
Must follow:
- File organization, naming conventions, import statements from existing code
- Error handling and validation patterns
- Use @lenne.tech/nest-server first, add packages as last resort
Test quality:
- 80-100% coverage, self-documenting, independent, repeatable, fast
NEVER use declare keyword - it prevents decorators from working!
Autonomous Execution
Work autonomously: Create tests, run tests, fix code, iterate Steps 3-5, use nest-server-generator skill
Only ask when: Story ambiguous, security changes needed, new packages, architectural decisions, persistent failures
Final Report
When all tests pass, provide comprehensive report including:
- Story name, tests created (location, count, coverage)
- Implementation summary (modules/objects/properties created/modified)
- Test results (all passing, scenarios summary)
- Code quality (patterns followed, security preserved, dependencies, refactoring, indexes)
- Security review (auth/authz, validation, data exposure, ownership, injection prevention, errors, security tests)
- Files modified (with changes description)
/lt-dev:reviewresults (general security scan findings)- Next steps (recommendations)
Common Patterns
Complete patterns and examples: examples.md and reference.md
Study existing tests first! Common patterns:
- Create test users via
/auth/signin, set roles/verified via DB - REST requests:
testHelper.rest('/api/...', { method, payload, token, statusCode }) - GraphQL queries:
testHelper.graphQl({ name, type, arguments, fields }, { token }) - Test organization:
describeblocks for Happy Path, Error Cases, Edge Cases
Integration with generating-nest-servers
During Step 4 (Implementation), use generating-nest-servers skill for:
- Module creation (
lt server module) - Object creation (
lt server object) - Adding properties (
lt server addProp) - Understanding existing code (Services, Controllers, Resolvers, Models, DTOs)
Best Practice: Invoke skill for NestJS component work rather than manual editing.