{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreihlp3mwbws6cxow5yuum5fmgrfg55anbulzz7hwydxh2ajn2f5xhu",
"uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3monb2xcx57m2"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreiboapcg74s5qqdkq55kkxgiz2zlifxgambik6qt3h4r3kc4u7vrte"
},
"mimeType": "image/webp",
"size": 62580
},
"path": "/bhaumik-viitor/unit-test-ai-guide-zero-hallucination-cross-stack-standard-1p28",
"publishedAt": "2026-06-19T11:38:06.000Z",
"site": "https://dev.to",
"tags": [
"ai",
"testing",
"tutorial",
"webdev",
"@nestjs",
"@testing-library",
"@codebase",
"@golevelup",
"@pytest.mark.asyncio",
"@pytest.fixture",
"@angular-builders",
"@nodomain.com",
"@executeautomation",
"@modelcontextprotocol",
"@types",
"@vitest"
],
"textContent": "> **Focus:** Unit Tests ONLY — no integration, no E2E\n>\n> **Stacks:** Node.js (NestJS/Express) · React.js · Python · Angular · Laravel\n>\n> **Goal:** AI generates unit tests consistently, deterministically, without hallucination\n>\n> **IDE:** Cursor (Primary) + Claude (Secondary)\n\n## Part 1 — Best Single Library Per Stack (Final Decision)\n\nDo not mix libraries. Pick one per stack, configure it fully, never deviate.\n\n| Stack | Library | Why This One |\n\n|---|---|---|\n\n| **Node.js / NestJS / Express** | `Jest` | Native DI mocking, `@nestjs/testing` built around it, widest ecosystem |\n\n| **React.js** | `Vitest` + `@testing-library/react` | Native Vite/ESM support, Jest-compatible API, 3–10x faster |\n\n| **Python** | `pytest` | De facto standard, fixture system eliminates boilerplate, best plugin ecosystem |\n\n| **Angular** | `Jest` (replace Karma) | Karma is deprecated in Angular 17+; Jest is the official migration target |\n\n| **Laravel** | `Pest` | Modern syntax, built on PHPUnit, higher signal-to-noise ratio |\n\n> **Rule:** If someone suggests a second library for the same stack, reject it. One library per stack, configured once, followed always.\n\n## Part 2 — IDE: Cursor (Only Choice for This Goal)\n\n### Why Cursor and Not VS Code / WebStorm\n\n| Capability | Cursor | VS Code + Copilot | WebStorm |\n\n|---|---|---|---|\n\n| Project-level AI rules | ✅ `.cursor/rules/` | ❌ | ❌ |\n\n| Codebase-aware context | ✅ `@codebase` | Partial | Partial |\n\n| Run terminal + read output | ✅ Composer | ❌ | ❌ |\n\n| Multi-file generation | ✅ Agent mode | Limited | ❌ |\n\n| Custom instructions per filetype | ✅ | ❌ | ❌ |\n\n| MCP server integration | ✅ | ❌ | ❌ |\n\nCursor's `.cursor/rules/` system is the **only IDE-native mechanism** that injects persistent, project-scoped instructions into every AI interaction — this is what prevents hallucination at the source.\n\n### Cursor Setup for This Project\n\n\n\n project-root/\n\n\n ├── .cursor/\n\n\n │ └── rules/\n\n\n │ ├── unit-test-global.mdc ← applies to all files\n\n\n │ ├── unit-test-nestjs.mdc ← applies to *.service.ts, *.guard.ts\n\n\n │ ├── unit-test-react.mdc ← applies to *.tsx, *.component.tsx\n\n\n │ ├── unit-test-python.mdc ← applies to *.py\n\n\n │ ├── unit-test-angular.mdc ← applies to *.component.ts, *.service.ts\n\n\n │ └── unit-test-laravel.mdc ← applies to *Service.php, *Model.php\n\n\n ├── CLAUDE.md ← Claude project memory file\n\n\n └── ...\n\n\n\n\n## Part 3 — Cursor Rules Files (Anti-Hallucination Core)\n\nThese files are injected into every AI prompt automatically when working on matching files.\n\n### 3.1 Global Unit Test Rule\n\n**File:`.cursor/rules/unit-test-global.mdc`**\n\n\n\n\n ---\n\n\n description: Global unit test rules — applies to all files in this project\n\n\n globs: [\"**/*.spec.ts\", \"**/*.test.ts\", \"**/*.spec.tsx\", \"**/*_test.py\", \"**/Test*.php\"]\n\n\n alwaysApply: true\n\n\n ---\n\n\n\n\n\n # Unit Test Contract — MUST FOLLOW, NO EXCEPTIONS\n\n\n\n\n\n ## What is a unit test here\n\n\n - Tests ONE class or function in complete isolation\n\n\n - ALL external dependencies are mocked — no real DB, no real HTTP, no real file system\n\n\n - Each test runs independently — no shared mutable state between tests\n\n\n - Runs in < 100ms\n\n\n\n\n\n ## Structure: AAA — Always, Without Exception\n\n\n Every test MUST have these three sections, with comments:\n\n\n\n\n// Arrange — set up inputs, mocks, expected values\n\n// Act — call the single function/method under test\n\n// Assert — verify exactly one outcome\n\n\n\n\n\n\n\n ## Naming Convention — Mandatory\n\n\n - File: `[module-name].spec.ts` or `[module-name].unit.spec.ts`\n\n\n - Describe block: exact class or function name\n\n\n - It/test block: \"should [expected behavior] when [specific condition]\"\n\n\n GOOD: \"should throw NotFoundException when user does not exist\"\n\n\n BAD: \"user not found\", \"test 1\", \"works correctly\"\n\n\n\n\n\n ## What to test per function — minimum coverage\n\n\n 1. Happy path — valid input → expected output\n\n\n 2. Null/undefined input — how does it fail safely\n\n\n 3. Empty input — empty string, empty array, zero\n\n\n 4. Error path — when dependency throws, what happens\n\n\n 5. Boundary — max length, negative numbers, boolean edge\n\n\n\n\n\n ## What NOT to include in unit tests\n\n\n - Database queries (mock the repository)\n\n\n - HTTP calls (mock the service/axios/fetch)\n\n\n - File system operations (mock fs)\n\n\n - Timer behavior (use jest.useFakeTimers)\n\n\n - Random values (mock Math.random or faker with fixed seed)\n\n\n\n\n\n ## AI Instruction\n\n\n When asked to generate a unit test:\n\n\n 1. READ the function signature and types first\n\n\n 2. IDENTIFY all dependencies (constructor args, imported modules)\n\n\n 3. MOCK every dependency — never use real implementations\n\n\n 4. Generate minimum 4 test cases per method (happy, null, error, edge)\n\n\n 5. NEVER import from test doubles or assume what isn't in the source file\n\n\n 6. If you are unsure of a type — ASK, do not assume\n\n\n\n\n### 3.2 NestJS Rule\n\n**File:`.cursor/rules/unit-test-nestjs.mdc`**\n\n\n\n\n ---\n\n\n description: NestJS unit test rules\n\n\n globs: [\"src/**/*.service.ts\", \"src/**/*.guard.ts\", \"src/**/*.interceptor.ts\", \"src/**/*.pipe.ts\"]\n\n\n ---\n\n\n\n\n\n # NestJS Unit Test Rules\n\n\n\n\n\n ## Library: Jest + @nestjs/testing + jest-mock-extended\n\n\n\n\n\n ## Always use Test.createTestingModule\n\n\n\n\n\ntypescript\n\nconst module = await Test.createTestingModule({\n\nproviders: [\n\n\n SubjectService,\n\n\n { provide: DependencyService, useValue: mockDependency }\n\n\n]\n\n}).compile();\n\n\n\n\n\n\n\n ## Mock Pattern — jest-mock-extended\n\n\n\n\n\ntypescript\n\nimport { createMock } from '@golevelup/ts-jest';\n\n// OR\n\nimport { mock } from 'jest-mock-extended';\n\nconst mockUserRepo = mock();\n\n\n\n\n\n\n\n ## Never\n\n\n - Never use `new SubjectService()` directly — always use TestingModule\n\n\n - Never let `useValue` contain real implementations\n\n\n - Never test more than one service per describe block\n\n\n\n\n\n ## Repository Mock Template\n\n\n\n\n\ntypescript\n\nconst mockRepo = {\n\nfindOne: jest.fn(),\n\nsave: jest.fn(),\n\ndelete: jest.fn(),\n\nfindAll: jest.fn(),\n\nupdate: jest.fn(),\n\n};\n\n\n\n\n\n\n\n ## Required imports for every NestJS spec file\n\n\n\n\n\ntypescript\n\nimport { Test, TestingModule } from '@nestjs/testing';\n\nimport { NotFoundException, BadRequestException } from '@nestjs/common';\n\n\n\n\n\n\n\nmarkdown\n\n### 3.3 React Rule\n\n**File:`.cursor/rules/unit-test-react.mdc`**\n\n\n\n\n ---\n\n\n description: React unit test rules\n\n\n globs: [\"src/**/*.tsx\", \"src/**/*.component.tsx\", \"src/hooks/**/*.ts\"]\n\n\n ---\n\n\n\n\n\n # React Unit Test Rules\n\n\n\n\n\n ## Library: Vitest + @testing-library/react + @testing-library/user-event\n\n\n\n\n\n ## Required setup in vitest.config.ts\n\n\n\n\n\ntypescript\n\nexport default defineConfig({\n\ntest: {\n\n\n environment: 'jsdom',\n\n\n globals: true,\n\n\n setupFiles: ['./src/test/setup.ts']\n\n\n}\n\n})\n\n\n\n\n\n\n\n ## setup.ts\n\n\n\n\n\ntypescript\n\nimport '@testing-library/jest-dom';\n\n\n\n\n\n\n\n ## Component test structure\n\n\n\n\n\ntypescript\n\nimport { render, screen } from '@testing-library/react';\n\nimport userEvent from '@testing-library/user-event';\n\nimport { vi } from 'vitest';\n\n\n\n\n\n\n\n ## Mocking rules for React\n\n\n - Mock ALL hooks that call APIs: `vi.mock('../hooks/useUsers')`\n\n\n - Mock ALL context providers — wrap with test-specific providers\n\n\n - Mock router: use `MemoryRouter` from react-router-dom\n\n\n - NEVER mock internal state (useState) — test behavior, not implementation\n\n\n\n\n\n ## Query priority (RTL best practice — mandatory)\n\n\n 1. getByRole — prefer always\n\n\n 2. getByLabelText — for forms\n\n\n 3. getByText — for content\n\n\n 4. getByTestId — LAST RESORT only, requires data-testid attribute\n\n\n\n\n\n ## User event — always use userEvent, never fireEvent\n\n\n\n\n\ntypescript\n\nconst user = userEvent.setup();\n\nawait user.click(button);\n\nawait user.type(input, 'value');\n\n\n\n\n\n\n\n ## Custom hook testing\n\n\n\n\n\ntypescript\n\nimport { renderHook, act } from '@testing-library/react';\n\nconst { result } = renderHook(() => useMyHook());\n\nact(() => result.current.doSomething());\n\nexpect(result.current.value).toBe('expected');\n\n\n\n\n\n\n\ntypescript\n\n### 3.4 Python Rule\n\n**File:`.cursor/rules/unit-test-python.mdc`**\n\n\n\n\n ---\n\n\n description: Python unit test rules\n\n\n globs: [\"**/*.py\", \"!**/*migrations*\"]\n\n\n ---\n\n\n\n\n\n # Python Unit Test Rules\n\n\n\n\n\n ## Library: pytest + pytest-mock + factory-boy\n\n\n\n\n\n ## File naming\n\n\n - Source: `app/services/user_service.py`\n\n\n - Test: `tests/unit/test_user_service.py`\n\n\n - ALWAYS mirror the source directory structure under tests/unit/\n\n\n\n\n\n ## Class under test — always use dependency injection\n\n\n\n\n\npython\n\n# GOOD — injectable dependency\n\nclass UserService:\n\n\n def __init__(self, repo: UserRepository):\n\n\n self.repo = repo\n\n\n# BAD — untestable\n\nclass UserService:\n\n\n def __init__(self):\n\n\n self.repo = UserRepository() # can't mock this\n\n\n\n\n\n\n\n ## Mock pattern — pytest-mock (mocker fixture)\n\n\n\n\n\npython\n\ndef test_raises_when_user_not_found(mocker):\n\n\n mock_repo = mocker.Mock()\n\n\n mock_repo.find_by_id.return_value = None\n\n\n service = UserService(mock_repo)\n\n\n with pytest.raises(UserNotFoundException):\n\n\n service.get_user(\"missing-id\")\n\n\n\n\n\n\n\n ## Async tests — pytest-asyncio\n\n\n\n\n\npython\n\nimport pytest\n\n@pytest.mark.asyncio\n\nasync def test_async_service(mocker):\n\n\n mock_repo = mocker.AsyncMock()\n\n\n ...\n\n\n\n\n\n\n\n ## Fixture pattern — for shared setup\n\n\n\n\n\npython\n\n@pytest.fixture\n\ndef user_service(mocker):\n\n\n repo = mocker.Mock()\n\n\n return UserService(repo), repo\n\n\ndef test_get_user_happy_path(user_service):\n\n\n service, repo = user_service\n\n\n repo.find_by_id.return_value = User(id=\"1\", email=\"a@b.com\")\n\n\n result = service.get_user(\"1\")\n\n\n assert result.email == \"a@b.com\"\n\n\n\n\n\n\n\n ## Naming\n\n\n - File: `test_[module_name].py`\n\n\n - Function: `test_[expected_behavior]_when_[condition]`\n\n\n\n\n\nmarkdown\n\n### 3.5 Angular Rule\n\n**File:`.cursor/rules/unit-test-angular.mdc`**\n\n\n\n\n ---\n\n\n description: Angular unit test rules\n\n\n globs: [\"src/app/**/*.component.ts\", \"src/app/**/*.service.ts\", \"src/app/**/*.pipe.ts\", \"src/app/**/*.guard.ts\"]\n\n\n ---\n\n\n\n\n\n # Angular Unit Test Rules\n\n\n\n\n\n ## Library: Jest (NOT Karma — Karma is deprecated Angular 17+)\n\n\n\n\n\n ## Migration from Karma to Jest (one-time)\n\n\n\n\n\nbash\n\nng add @angular-builders/jest\n\n\n\n\n\n\n\n ## TestBed configuration — always minimal\n\n\n\n\n\ntypescript\n\nawait TestBed.configureTestingModule({\n\nimports: [ComponentUnderTest], // standalone components\n\nproviders: [\n\n\n { provide: UserService, useValue: mockUserService }\n\n\n]\n\n}).compileComponents();\n\n\n\n\n\n\n\n ## Service mock pattern\n\n\n\n\n\ntypescript\n\nconst mockUserService = {\n\ngetUser: jest.fn(),\n\ncreateUser: jest.fn(),\n\n};\n\n\n\n\n\n\n\n ## Component test — check DOM behavior, not internal state\n\n\n\n\n\ntypescript\n\nfixture.detectChanges(); // trigger ngOnInit\n\nconst button = fixture.debugElement.query(By.css('[data-testid=\"submit\"]'));\n\nbutton.nativeElement.click();\n\nfixture.detectChanges();\n\nexpect(fixture.debugElement.query(By.css('.error-msg'))).toBeTruthy();\n\n\n\n\n\n\n\n ## Pipe test — pure function, no TestBed needed\n\n\n\n\n\ntypescript\n\nit('should transform date correctly', () => {\n\nconst pipe = new DateFormatPipe();\n\nexpect(pipe.transform(new Date('2024-01-01'))).toBe('Jan 1, 2024');\n\n});\n\n\n\n\n\n\n\n ## Guard/resolver test — inject and call directly\n\n\n\n\n\ntypescript\n\nconst guard = TestBed.inject(AuthGuard);\n\nconst result = await guard.canActivate(mockRoute, mockState);\n\nexpect(result).toBe(false);\n\n\n\n\n\n\n\npython\n\n### 3.6 Laravel Rule\n\n**File:`.cursor/rules/unit-test-laravel.mdc`**\n\n\n\n\n ---\n\n\n description: Laravel unit test rules\n\n\n globs: [\"app/Services/**/*.php\", \"app/Models/**/*.php\", \"app/Http/Requests/**/*.php\", \"app/Actions/**/*.php\"]\n\n\n ---\n\n\n\n\n\n # Laravel Unit Test Rules\n\n\n\n\n\n ## Library: Pest (NOT PHPUnit directly — Pest wraps it with better syntax)\n\n\n\n\n\n ## File location\n\n\n - Source: `app/Services/UserService.php`\n\n\n - Test: `tests/Unit/Services/UserServiceTest.php`\n\n\n\n\n\n ## Class under test — use constructor injection\n\n\n\n\n\nphp\n\nclass UserService {\n\n\n public function __construct(\n\n\n private readonly UserRepositoryInterface $repo\n\n\n ) {}\n\n\n}\n\n\n\n\n\n\n\n ## Mock pattern — Mockery (included with Pest/PHPUnit)\n\n\n\n\n\nphp\n\nit('throws exception when user not found', function () {\n\n\n // Arrange\n\n\n $repo = Mockery::mock(UserRepositoryInterface::class);\n\n\n $repo->shouldReceive('findById')->once()->andReturn(null);\n\n\n $service = new UserService($repo);\n\n\n\n\n\n // Act & Assert\n\n\n expect(fn () => $service->getUser('abc'))\n\n\n ->toThrow(UserNotFoundException::class);\n\n\n});\n\n\n\n\n\n\n\n ## Data provider pattern (Pest datasets)\n\n\n\n\n\nphp\n\nit('validates email format', function (string $email, bool $valid) {\n\n\n expect(validateEmail($email))->toBe($valid);\n\n\n})->with([\n\n\n ['valid@email.com', true],\n\n\n ['notanemail', false],\n\n\n ['', false],\n\n\n ['@nodomain.com', false],\n\n\n]);\n\n\n\n\n\n\n\n ## What NOT to do in unit tests\n\n\n - Never call $this->get() or $this->post() — that is feature testing\n\n\n - Never use RefreshDatabase — that is feature/integration testing\n\n\n - Never resolve from service container — inject directly\n\n\n\n\n\nmarkdown\n\n## Part 4 — CLAUDE.md (Claude Project Memory)\n\nPlace this at project root. Claude reads it on every session automatically.\n\n**File:`CLAUDE.md`**\n\n\n\n\n # Project: [Your Project Name]\n\n\n # Claude Unit Test Instructions\n\n\n\n\n\n ## Stack\n\n\n - Backend: NestJS + TypeScript\n\n\n - Frontend: React + Vite + TypeScript\n\n\n - Testing: Jest (NestJS) + Vitest (React) + Pest (Laravel) + pytest (Python)\n\n\n\n\n\n ## Unit Test Rules — NON-NEGOTIABLE\n\n\n\n\n\n ### When asked to generate a unit test:\n\n\n 1. NEVER generate integration or E2E tests unless explicitly asked\n\n\n 2. ALWAYS mock every external dependency\n\n\n 3. ALWAYS follow AAA (Arrange-Act-Assert) with comments\n\n\n 4. ALWAYS generate minimum 4 cases: happy path, null/empty, error thrown, edge case\n\n\n 5. NEVER use `any` type in TypeScript tests\n\n\n 6. NEVER leave `TODO` comments in generated tests\n\n\n\n\n\n ### Naming\n\n\n - NestJS: `[name].service.spec.ts` inside same directory as source\n\n\n - React: `[ComponentName].test.tsx` inside `__tests__/` or same directory\n\n\n - Python: `test_[module].py` under `tests/unit/`\n\n\n - Laravel: `[Name]Test.php` under `tests/Unit/`\n\n\n\n\n\n ### Test should describe BEHAVIOR, not implementation\n\n\n GOOD: \"should return empty array when no users match search\"\n\n\n BAD: \"test getUserByFilter\"\n\n\n\n\n\n ### If you are unsure of the correct mock structure:\n\n\n - Ask what the dependency interface looks like\n\n\n - Do NOT invent method names that don't exist in the source\n\n\n\n\n\n ### Coverage target: 80% lines, 75% branches per module\n\n\n\n\n\n ## Project Structure Reference\n\n\n src/\n\n\n modules/\n\n\n users/\n\n\n users.service.ts\n\n\n users.service.spec.ts ← unit test lives here\n\n\n users.repository.ts\n\n\n users.repository.spec.ts\n\n\n\n\n## Part 5 — MCP Servers for Unit Testing in Cursor + Claude\n\nMCP (Model Context Protocol) servers extend AI capabilities. For unit testing, these are the relevant ones:\n\n### 5.1 Filesystem MCP (Built-in Cursor — use directly)\n\nCursor already has filesystem access. No extra MCP needed to read source files and generate tests. The `.cursor/rules/` system handles this.\n\n### 5.2 Available MCP Servers for Testing Workflows\n\n**1.`@executeautomation/playwright-mcp-server`**\n\n * Scope: E2E only — not relevant for unit tests\n\n * Skip for this use case\n\n\n\n\n**2.`@modelcontextprotocol/server-filesystem`**\n\n * Gives Claude access to project files\n\n * Use in Claude Desktop to read source → generate tests\n\n * Install: configured in `claude_desktop_config.json`\n\n\n\n\n**3. Custom Testing MCP (Build This — Highest Value)**\n\nBuild a lightweight MCP server that:\n\n * Reads a source file\n\n * Extracts function signatures + types\n\n * Returns structured JSON to Claude\n\n * Claude generates tests from structure, not guesswork\n\n\n\n\n\n\n\n // mcp-test-generator/src/index.ts\n\n\n import { Server } from '@modelcontextprotocol/sdk/server/index.js';\n\n\n import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\n\n\n import * as ts from 'typescript';\n\n\n\n\n\n const server = new Server({ name: 'test-generator', version: '1.0.0' }, {\n\n\n capabilities: { tools: {} }\n\n\n });\n\n\n\n\n\n server.setRequestHandler('tools/call', async (req) => {\n\n\n if (req.params.name === 'extract_signatures') {\n\n\n const { filePath } = req.params.arguments;\n\n\n const signatures = extractFunctionSignatures(filePath); // parse AST\n\n\n return { content: [{ type: 'text', text: JSON.stringify(signatures) }] };\n\n\n }\n\n\n });\n\n\n\n\n\n function extractFunctionSignatures(filePath: string) {\n\n\n // Use TypeScript compiler API to extract:\n\n\n // - class name\n\n\n // - method names\n\n\n // - parameter types\n\n\n // - return types\n\n\n // - injected dependencies (constructor params)\n\n\n const program = ts.createProgram([filePath], {});\n\n\n const sourceFile = program.getSourceFile(filePath);\n\n\n // ... AST traversal\n\n\n return { className, methods, dependencies };\n\n\n }\n\n\n\n\n**Why this matters:** When Claude receives typed signatures instead of raw source code, it cannot hallucinate — it generates tests from concrete types, not guesses.\n\n### 5.3 Claude Desktop MCP Config\n\n\n\n // ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)\n\n\n // %APPDATA%\\Claude\\claude_desktop_config.json (Windows)\n\n\n {\n\n\n \"mcpServers\": {\n\n\n \"filesystem\": {\n\n\n \"command\": \"npx\",\n\n\n \"args\": [\"-y\", \"@modelcontextprotocol/server-filesystem\", \"/path/to/your/project\"]\n\n\n },\n\n\n \"test-generator\": {\n\n\n \"command\": \"node\",\n\n\n \"args\": [\"/path/to/your/mcp-test-generator/dist/index.js\"]\n\n\n }\n\n\n }\n\n\n }\n\n\n\n\n### 5.4 Cursor MCP Config\n\n\n\n // .cursor/mcp.json (project-level)\n\n\n {\n\n\n \"mcpServers\": {\n\n\n \"test-generator\": {\n\n\n \"command\": \"node\",\n\n\n \"args\": [\"./tools/mcp-test-generator/dist/index.js\"]\n\n\n }\n\n\n }\n\n\n }\n\n\n\n\n## Part 6 — Anti-Hallucination Prompt Templates\n\nThe root cause of AI hallucination in tests: AI invents method names, wrong mock structures, non-existent imports. These prompts eliminate that.\n\n### 6.1 Cursor Cmd+K Prompt (Use This Exactly)\n\n\n\n Generate a unit test file for this [NestJS service / React component / Python class / Angular service / Laravel service].\n\n\n\n\n\n Rules:\n\n\n - Library: [Jest / Vitest / pytest / Jest / Pest]\n\n\n - Test only THIS file — no integration\n\n\n - Mock ALL dependencies listed in the constructor/props\n\n\n - Follow AAA pattern with comments\n\n\n - Minimum 4 test cases per public method\n\n\n - Use ONLY methods/properties that exist in this source file\n\n\n - Do NOT import anything not shown in the source\n\n\n - Naming: \"should [behavior] when [condition]\"\n\n\n\n\n### 6.2 Claude Prompt (For CLAUDE.md aware sessions)\n\n\n\n Read [filename].\n\n\n Extract:\n\n\n 1. Class/function name\n\n\n 2. All public methods with parameter types and return types\n\n\n 3. All constructor dependencies (for mocking)\n\n\n\n\n\n Then generate the unit test file following CLAUDE.md rules.\n\n\n Do NOT assume any method exists unless you can see it in the source.\n\n\n\n\n### 6.3 Batch Test Generation Prompt\n\n\n\n For each file in [directory], generate a corresponding .spec.ts file.\n\n\n Process one file at a time.\n\n\n For each file:\n\n\n 1. Read the source\n\n\n 2. List the public methods you found (confirm before generating)\n\n\n 3. Generate the test file\n\n\n 4. Show the test file path\n\n\n\n\n\n Do not proceed to the next file until the current one is confirmed.\n\n\n\n\n## Part 7 — Project Structure Convention\n\nAll stacks follow the same proximity principle: **test file lives next to source file**.\n\n### Node.js / NestJS / Angular\n\n\n\n src/\n\n\n modules/\n\n\n users/\n\n\n users.service.ts\n\n\n users.service.spec.ts ← unit test\n\n\n users.repository.ts\n\n\n users.repository.spec.ts ← unit test\n\n\n users.guard.ts\n\n\n users.guard.spec.ts ← unit test\n\n\n\n\n### React\n\n\n\n src/\n\n\n components/\n\n\n UserCard/\n\n\n UserCard.tsx\n\n\n UserCard.test.tsx ← unit test\n\n\n UserCard.stories.tsx ← storybook (optional)\n\n\n hooks/\n\n\n useUserData.ts\n\n\n useUserData.test.ts ← unit test\n\n\n\n\n### Python\n\n\n\n app/\n\n\n services/\n\n\n user_service.py\n\n\n tests/\n\n\n unit/\n\n\n services/\n\n\n test_user_service.py ← mirrors app/ structure\n\n\n\n\n### Laravel\n\n\n\n app/\n\n\n Services/\n\n\n UserService.php\n\n\n tests/\n\n\n Unit/\n\n\n Services/\n\n\n UserServiceTest.php ← mirrors app/ structure\n\n\n\n\n## Part 8 — CI Enforcement\n\nAll of this means nothing without enforcement. The pipeline is the final gate.\n\n### GitHub Actions — Multi-stack parallel\n\n\n\n name: Unit Tests\n\n\n\n\n\n on: [push, pull_request]\n\n\n\n\n\n jobs:\n\n\n nestjs-unit:\n\n\n runs-on: ubuntu-latest\n\n\n steps:\n\n\n - uses: actions/checkout@v4\n\n\n - uses: actions/setup-node@v4\n\n\n with: { node-version: '20' }\n\n\n - run: npm ci\n\n\n - run: npm run test:unit -- --coverage --coverageThreshold='{\"global\":{\"lines\":80}}'\n\n\n\n\n\n react-unit:\n\n\n runs-on: ubuntu-latest\n\n\n steps:\n\n\n - uses: actions/checkout@v4\n\n\n - uses: actions/setup-node@v4\n\n\n with: { node-version: '20' }\n\n\n - run: npm ci\n\n\n - run: npx vitest run --coverage\n\n\n\n\n\n python-unit:\n\n\n runs-on: ubuntu-latest\n\n\n steps:\n\n\n - uses: actions/checkout@v4\n\n\n - uses: actions/setup-python@v5\n\n\n with: { python-version: '3.12' }\n\n\n - run: pip install -r requirements-dev.txt\n\n\n - run: pytest tests/unit/ --cov=app --cov-fail-under=80\n\n\n\n\n\n laravel-unit:\n\n\n runs-on: ubuntu-latest\n\n\n steps:\n\n\n - uses: actions/checkout@v4\n\n\n - uses: shivammathur/setup-php@v2\n\n\n with: { php-version: '8.3' }\n\n\n - run: composer install\n\n\n - run: ./vendor/bin/pest --coverage --min=80\n\n\n\n\n### Pre-commit Hook (Husky — Node stacks)\n\n\n\n // package.json\n\n\n {\n\n\n \"lint-staged\": {\n\n\n \"src/**/*.service.ts\": [\"jest --findRelatedTests --passWithNoTests\"],\n\n\n \"src/**/*.tsx\": [\"vitest related --run\"]\n\n\n }\n\n\n }\n\n\n\n\n## Part 9 — Quick Start Checklist\n\n### New Project\n\n\n\n □ Install test library (one per stack — see Part 1)\n\n\n □ Configure jest.config.ts / vitest.config.ts / pytest.ini / pest.php\n\n\n □ Create .cursor/rules/ directory with all rule files from Part 3\n\n\n □ Create CLAUDE.md at project root from Part 4\n\n\n □ Add coverage thresholds to config\n\n\n □ Add pre-commit hooks (Husky for Node, pre-commit for Python)\n\n\n □ Add CI workflow from Part 8\n\n\n □ Create test factory files (one per major entity)\n\n\n □ Write first failing test → implement → green → commit\n\n\n\n\n### Existing Project\n\n\n\n □ Add test library without touching existing code\n\n\n □ Add .cursor/rules/ + CLAUDE.md\n\n\n □ Run coverage on existing code → document current baseline\n\n\n □ Pick 3 critical services → generate tests → set as new baseline\n\n\n □ Add CI with threshold = current baseline (not ideal — current)\n\n\n □ Raise threshold by 5% per sprint\n\n\n\n\n## Appendix — Install Commands Per Stack\n\n### NestJS\n\n\n\n npm install --save-dev jest ts-jest @types/jest @nestjs/testing jest-mock-extended @golevelup/ts-jest\n\n\n npx ts-jest config:init\n\n\n\n\n### React (Vite)\n\n\n\n npm install --save-dev vitest @vitest/coverage-v8 @testing-library/react @testing-library/user-event @testing-library/jest-dom jsdom\n\n\n\n\n### Python\n\n\n\n pip install pytest pytest-cov pytest-mock pytest-asyncio factory-boy\n\n\n\n\n### Angular\n\n\n\n ng add @angular-builders/jest\n\n\n npm install --save-dev jest jest-preset-angular @types/jest\n\n\n\n\n### Laravel\n\n\n\n composer require pestphp/pest pestphp/pest-plugin-laravel --dev\n\n\n ./vendor/bin/pest --init\n\n\n\n\n_Last updated: 2025 — verify library major versions before adopting_",
"title": "Unit Test AI Guide — Zero Hallucination, Cross-Stack Standard"
}