tdd
Test-Driven Development (TDD)
Apply this skill to execute strict test-first development: write a failing test, implement the minimum code to pass, then refactor while keeping tests green.
Decide when to use TDD
Use this decision table:
| Situation | Use this skill? | Default action |
|---|---|---|
| New utility function | Yes | Run TDD cycle immediately |
| New Effect service | Yes | Define behavior through tests first |
| Complex business logic | Yes | Lock behavior with tests before implementation |
| Bug fix | Yes | Reproduce bug with failing test first |
| UI styling/layout-only change | No | Use standard implementation flow |
| Exploratory prototyping | No | Prototype first, then switch to TDD once behavior stabilizes |
| TRPC endpoint (simple CRUD) | Usually no | Use testing-patterns by default; use this skill only when user explicitly requests test-first/RGR |
Execute Red-Green-Refactor
Follow one behavior at a time through three phases.
1. RED - Write Failing Test First
import { describe, expect, it } from "vitest";
import { calculateDiscount } from "../calculate-discount";
describe("calculateDiscount", () => {
it("applies 10% discount for orders over 100", () => {
// This test fails first: function does not exist yet
expect(calculateDiscount(150)).toBe(135);
});
});
Run test to see it fail:
bun run vitest run packages/common/src/__tests__/calculate-discount.test.ts
2. GREEN - Implement Minimum Code
Write the minimum code to make the test pass:
// packages/common/src/calculate-discount.ts
export function calculateDiscount(amount: number): number {
if (amount > 100) {
return amount * 0.9;
}
return amount;
}
Run test to see it pass:
bun run vitest run packages/common/src/__tests__/calculate-discount.test.ts
3. REFACTOR - Improve Without Breaking Behavior
Improve code quality while keeping tests green:
const DISCOUNT_THRESHOLD = 100;
const DISCOUNT_RATE = 0.1;
export function calculateDiscount(amount: number): number {
if (amount <= DISCOUNT_THRESHOLD) {
return amount;
}
return amount * (1 - DISCOUNT_RATE);
}
Run tests again to verify refactoring didn't break anything.
For extended examples, failure diagnostics, and iterative expansions, read references/red-green-refactor.md.
Keep test scope lean
Prefer lighter tests first and escalate only when needed:
- Unit tests (preferred) - Pure functions and Effect services with mock layers.
- TRPC integration tests - Full TRPC + persistence behavior.
- E2E tests - Browser-level flows and user journeys.
| Situation | Test Type | Action |
|---|---|---|
| Pure function, parser, util | Unit | Write immediately |
| Effect service with dependencies | Unit with mock layers | Write immediately |
| TRPC procedure (DB logic) | TRPC integration | Follow testing-patterns decision process |
| User-facing flow, UI behavior | E2E | Follow testing-patterns decision process |
Apply Effect-specific TDD patterns
Use test-first service design with explicit layers.
- Define interface via test - Make behavior explicit before implementation.
- Create mock layer - Isolate dependencies and keep tests deterministic.
- Implement service - Satisfy the tests with the minimal behavior.
- Refactor - Improve readability and structure while preserving behavior.
import { describe, expect, it } from "@effect/vitest";
import { Effect, Layer } from "effect";
describe("PricingService", () => {
// 1. Define what the service should do via tests
it.effect("calculates base price without discount", () =>
Effect.gen(function* () {
const service = yield* PricingService;
const result = yield* service.calculatePrice({
itemId: "item-1",
quantity: 2,
});
expect(result.total).toBe(200);
}).pipe(Effect.provide(testLayer)),
);
it.effect("applies bulk discount for quantity > 10", () =>
Effect.gen(function* () {
const service = yield* PricingService;
const result = yield* service.calculatePrice({
itemId: "item-1",
quantity: 15,
});
expect(result.total).toBe(1350); // 15% discount
}).pipe(Effect.provide(testLayer)),
);
});
Mock Layer Factory Pattern
// Create parameterized mock layers for different test scenarios
const createMockInventoryLayer = (inventory: Map<string, number>) =>
Layer.succeed(InventoryService, {
getStock: (itemId) => Effect.succeed(inventory.get(itemId) ?? 0),
reserveStock: (itemId, qty) => Effect.succeed(void 0),
});
// Use in tests
const testLayer = PricingService.layer.pipe(
Layer.provide(createMockInventoryLayer(new Map([["item-1", 100]]))),
);
For deeper guidance (error-path testing, layer composition, anti-vi.mock() rationale), read references/effect-tdd-patterns.md.
Use project-convention test locations (examples)
Treat these paths as project conventions/examples; adapt if a repository uses different test layout.
| Code Location | Test Location |
|---|---|
packages/X/src/file.ts |
packages/X/src/__tests__/file.test.ts |
apps/web-app/src/infrastructure/trpc/routers/X.ts |
apps/web-app/src/__tests__/X.test.ts |
apps/web-app/src/routes/** |
apps/web-app/e2e/feature.e2e.ts |
Use supporting references for depth
Load targeted references instead of expanding this file during execution:
- Use
references/commands.mdfor runnable command patterns. - Use
references/anti-patterns.mdfor failure-mode walkthroughs and corrections.
Resources
references/
red-green-refactor.md- Detailed TDD cycle workflow with exampleseffect-tdd-patterns.md- Effect service testing, mock layers, error casestest-first-examples.md- Step-by-step TDD examples for this codebasecommands.md- Command catalog and correct/incorrect invocation patternsanti-patterns.md- Detailed TDD anti-pattern walkthroughs with fixes
Related Skills
testing-patterns- Test syntax, TRPC integration tests, E2E patternseffect-ts- Effect service design, layers, error handling
More from blogic-cz/blogic-marketplace
marketing-expert
This skill should be used when writing or rewriting marketing copy for software products, including positioning, messaging, homepage rewrite work, landing pages, product descriptions, conversion-focused updates, and sales-enablement content. Produces clear, truthful, high-performing SaaS copy.
97requirements
This skill should be used when clarifying a feature, writing a requirements spec, running a structured discovery session, or when users mention requirements-start, requirements-status, requirements-current, requirements-list, requirements-remind, or requirements-end.
78frontend-design
This skill should be used when a task requires designing or implementing frontend UI (components, pages, layouts, styling) and no more specialized frontend skill is a better fit. It guides production-grade, brand-consistent visual implementation with distinctive but controlled aesthetics.
77testing-patterns
This skill should be used when implementing or reviewing testing workflows in template-ts projects, especially for testing, Vitest, Playwright, integration test, and mocking scenarios.
76git-workflow
Automates the full PR lifecycle — create or update a pull request, then aggressively monitor CI checks and review feedback in a continuous loop, fixing failures and addressing comments until the PR is fully green. Also covers push, branch creation, and branch sync workflows.
76debugging-with-opensrc
Load this skill when debugging behavior in external libraries by reading local OpenSrc mirrors (Effect, TanStack, TRPC, Drizzle, Better Auth, Sentry, Pino), or when docs conflict with runtime behavior and source-level verification is required.
75