# Test-Driven Development (TDD) Guide — MeshiTrack This guide serves as the definitive reference for writing, executing, and maintaining tests using a Test-Driven Development (TDD) methodology across all packages in the MeshiTrack monorepo. --- ## 1. Core Principles TDD is a software development workflow where you **write tests first, before writing implementation code**. This ensures high reliability, enforces strict scoping of features, prevents untested paths, and results in cleaner designs. ### The Red-Green-Refactor Cycle For every feature, schema, service, or route you build, follow this continuous loop: 1. **RED (Write a Failing Test)**: - Identify the small increment of behavior you want to add. - Write a test expressing that behavior. - Run the test and see it fail. **Compilation failures in TypeScript count as a valid "Red" state** for new types, interfaces, or class signatures. For logic, stub the signature and ensure it fails at runtime. 2. **GREEN (Write Minimal Code)**: - Write the absolute minimum production code required to satisfy the test (even if it's returning hardcoded values temporarily). - Run the test and see it pass. 3. **REFACTOR (Clean Up and Optimize)**: - Review your code (both production and test files). Remove duplication, improve readability, refine types, and optimize structure. - Run the test suite continuously to guarantee your refactoring did not break existing behavior (remains Green). --- ## 2. Directory Structure (Option B) To keep production bundles completely clean, all test files and mock utilities live in a root-level `tests/` folder matching the structure of `src/`. **Never place tests inside `src/`.** ``` packages/api/ ├── src/ # 100% Pure Production Code │ ├── modules/ │ │ └── products/ │ │ ├── products.service.ts │ │ └── products.repository.ts │ └── main.ts └── tests/ # 100% Test-First Files ├── helpers/ │ └── mock-repository.ts # Reusable mocking helpers ├── modules/ │ └── products/ │ ├── products.service.test.ts │ └── products.repository.test.ts └── schemas/ └── product.schema.test.ts ``` --- ## 3. Layer-by-Layer TDD Guide ### Layer 1: Shared Package (`packages/shared`) The shared package contains domain types, enums, and Zod schemas. 1. **Red**: Write a test in `packages/shared/tests/validation/feature.schemas.test.ts` asserting validation behaviors for correct, incorrect, and edge-case payloads. 2. **Green**: Implement the validation schema and types in `packages/shared/src/validation/feature.schemas.ts`. Run `npm run test -w packages/shared` to verify it passes. 3. **Refactor**: Clean up custom messages, dry up repeated Zod rules, and ensure exports are barrelled correctly in `index.ts`. --- ### Layer 2: Schema & Repository (`packages/api`) This layer handles persistence via Mongoose schemas and repositories. 1. **Red**: Write an integration test in `packages/api/tests/modules//.repository.test.ts` using `mongodb-memory-server` asserting query conditions, indexes, and `householdId` filtering. 2. **Green**: Create the Mongoose schema inside `src/schemas/` and the Repository class in `src/modules//`. All reads must use `.lean().exec()`. Verify tests pass. 3. **Refactor**: Optimize indexes and verify schema options (e.g. `timestamps: true`). --- ### Layer 3: Service Layer (`packages/api`) Services contain core business logic, transactional bounds, and use constructor-injected dependencies via Awilix. To eliminate boilerplate, import the `createMockRepository` helper from `tests/helpers/mock-repository.ts` to mock all repository methods automatically: ```typescript // packages/api/tests/modules/products/products.service.test.ts import { describe, it, expect, vi, beforeEach } from 'vitest'; import { ProductsService } from '../../../src/modules/products/products.service.js'; import { ProductsRepository } from '../../../src/modules/products/products.repository.js'; import { createMockRepository } from '../../helpers/mock-repository.js'; describe(ProductsService.name, () => { let service: ProductsService; let mockRepo: ReturnType>; beforeEach(() => { vi.clearAllMocks(); mockRepo = createMockRepository(ProductsRepository); service = new ProductsService({ productsRepository: mockRepo as any }); }); it('should find a product by household scope', async () => { // 1. RED: Write the test first, before service.findById exists mockRepo.findById.mockResolvedValue({ _id: '1', name: 'Apples' }); const result = await service.getById('1', 'hh1'); expect(result.name).toBe('Apples'); }); }); ``` 1. **Red**: Run the test. Verify it fails due to compilation (if the service/method is missing) or runtime failure (if stubbed). 2. **Green**: Implement constructor injection and the method in `src/modules//.service.ts`. Run the test to confirm it passes. 3. **Refactor**: Extract logic, reduce cognitive complexity, and ensure proper typing. --- ### Layer 4: Route Layer (`packages/api`) Fastify routes define endpoints, validate payloads, and resolve services. 1. **Red**: Write a route test in `tests/modules//.routes.test.ts` using Fastify's `app.inject()`. Mock the resolved service from the request DI scope. Assert status codes and payloads. 2. **Green**: Register the route plugin in `src/modules//.routes.ts`, wire validations via Zod type providers, and call the service. Verify it passes. 3. **Refactor**: Clean up endpoint structures, route prefixes, and schema schemas. --- ### Layer 5: Web API Client (`packages/web`) Frontend fetch wrapper utilizing MSW to mock backend requests. 1. **Red**: Write a service test in `packages/web/tests/services/.test.ts`. Configure MSW to mock the HTTP responses. Assert response modeling and mapping. 2. **Green**: Implement the service wrapper using fetch in `packages/web/src/services/`. Verify it passes. 3. **Refactor**: Clean up payload mappings and parameter serialization. --- ### Layer 6: Web UI Components (`packages/web`) React components and Next.js pages. 1. **Red**: Write a component test in `packages/web/tests/app/(dashboard)//.test.tsx` using React Testing Library. Use accessible role queries (`screen.getByRole`) to assert loading/rendering states and user interactions. 2. **Green**: Write the minimal TSX/JSX inside `packages/web/src/` to satisfy the tests. 3. **Refactor**: Refine tailwind utility classes, modularize components, and check accessibility (ARIA tags). --- ## 4. Tooling & DX Guidelines To make TDD fast and effective, use interactive watch-mode running continuously in the background. **Never run `npx`.** ### Continuous File-Specific Watch Mode (Fastest DX) To start watch mode for a specific test file during a Red-Green cycle: ```bash # Watch API Service tests npm run test:watch -w packages/api -- tests/modules/products/products.service.test.ts # Watch Web Component tests npm run test:watch -w packages/web -- tests/app/dashboard/ProductCard.test.tsx ``` ### Continuous Package-Wide Watch Mode To start watch mode for all tests in a package: ```bash # Continuous API package tests npm run test:watch -w packages/api # Continuous Web package tests npm run test:watch -w packages/web ``` ### Full-Suite Verification Before concluding any implementation phase, confirm that the entire verification gate is fully operational: ```bash npm run build # Enforces typescript compile across all packages npm run test:cov # Verifies all tests pass and meet strict coverage gates npm run lint # Validates static conventions ```