tdd

Test-Driven Development (TDD)

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "tdd" with this command: npx skills add blogic-cz/blogic-marketplace/blogic-cz-blogic-marketplace-tdd

Test-Driven Development (TDD)

Philosophy

TDD is a development methodology where you write tests BEFORE implementation. This ensures:

  • Clear requirements - Tests define expected behavior upfront

  • Better design - Forces you to think about interfaces first

  • Confidence - Every feature has test coverage from day one

  • Refactoring safety - Tests catch regressions immediately

When to Use TDD

Situation Use TDD? Reason

New utility function YES Pure functions are perfect for TDD

New Effect service YES Define interface via tests first

Complex business logic YES Tests clarify requirements

Bug fix YES Write failing test that reproduces bug first

UI component styling No Visual changes don't benefit from TDD

Exploratory prototyping No Requirements unclear, iterate first

TRPC endpoint (simple CRUD) Optional Ask user preference

Red-Green-Refactor Cycle

The TDD workflow follows 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 will FAIL - function doesn't 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

  1. GREEN - Minimal Implementation

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

  1. REFACTOR - Improve Without Breaking

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.

See references/red-green-refactor.md for detailed workflow examples.

Test Hierarchy (Prefer Simpler)

  • Unit tests (preferred) - Pure functions, Effect services with mock layers

  • TRPC Integration (ask first) - Full TRPC stack with PGlite

  • E2E (ask + justify) - Browser automation, slowest

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 Ask user first

User-facing flow, UI behavior E2E Ask + warn about maintenance

Effect TDD Patterns

Test-First Service Design

  • Define interface via test - What should the service do?

  • Create mock layer - Isolate dependencies

  • Implement service - Make tests pass

  • Refactor - Improve with confidence

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]]))), );

See references/effect-tdd-patterns.md for comprehensive Effect testing patterns.

Test File Locations

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

Commands

Unit & Integration Tests

Run all tests (unit + TRPC integration)

bun run test

Watch mode - re-run on file changes

bun run test:watch

Run with coverage report

bun run test:coverage

Run specific test file (FROM PROJECT ROOT, full path required)

bun run vitest run packages/common/src/tests/pagination.test.ts bun run vitest run apps/web-app/src/tests/formatters.test.ts

Run tests matching pattern

bun run vitest run -t "calculateDiscount"

E2E Tests

Install Playwright browsers (first time only)

bun run test:e2e:install

Run all E2E tests

bun run test:e2e

Run E2E with interactive UI

bun run test:e2e:ui

WRONG Syntax (DO NOT USE)

These DO NOT work:

bun run test packages/common/src/tests/file.test.ts # script doesn't accept path cd packages/common && bun run vitest run src/tests/file.test.ts # wrong cwd

TDD Anti-Patterns

  1. Writing Implementation First

// ❌ BAD - Implementation before test export function formatPrice(amount: number): string { return $${amount.toFixed(2)}; }

// Then writing test after - defeats TDD purpose

  1. Skipping the RED Phase

// ❌ BAD - Test passes immediately (you didn't verify it can fail) it("returns true", () => { expect(true).toBe(true); // This always passes! });

  1. Too Many Tests at Once

// ❌ BAD - Writing all tests before any implementation describe("UserService", () => { it("creates user", () => { /* ... / }); it("updates user", () => { / ... / }); it("deletes user", () => { / ... / }); it("lists users", () => { / ... / }); it("validates email", () => { / ... */ }); // 10 more tests... }); // Now you have 15 failing tests - overwhelming!

Correct approach: One test at a time. RED → GREEN → REFACTOR → next test.

  1. Skipping Refactor Phase

// ❌ BAD - Test passes, move on without cleanup export function calc(a: number, b: number, c: string): number { if (c === "add") return a + b; if (c === "sub") return a - b; if (c === "mul") return a * b; return 0; }

// ✅ GOOD - Refactor to cleaner design type Operation = "add" | "subtract" | "multiply";

const operations: Record<Operation, (a: number, b: number) => number> = { add: (a, b) => a + b, subtract: (a, b) => a - b, multiply: (a, b) => a * b, };

export function calculate(a: number, b: number, op: Operation): number { return operations[op](a, b); }

Resources

references/

  • red-green-refactor.md

  • Detailed TDD cycle workflow with examples

  • effect-tdd-patterns.md

  • Effect service testing, mock layers, error cases

  • test-first-examples.md

  • Step-by-step TDD examples for this codebase

Related Skills

  • testing-patterns

  • Test syntax, TRPC integration tests, E2E patterns

  • effect-ts

  • Effect service design, layers, error handling

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

code-review

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

git-workflow

No summary provided by upstream source.

Repository SourceNeeds Review
General

debugging-with-opensrc

No summary provided by upstream source.

Repository SourceNeeds Review
General

testing-patterns

No summary provided by upstream source.

Repository SourceNeeds Review