Install to Claude Code
npx -y skills add https://github.com/bmad-labs/skills --skill typescript-unit-testingDescription
typescript unit testing
SKILL.md
---
name: typescript-unit-testing
description: Unit testing for TypeScript/NestJS projects using Jest, @golevelup/ts-jest (DeepMocked/createMock), and in-memory databases, with AAA structure. Use whenever the user is working on `.spec.ts` files or asks to set up Jest, write/add tests for a service/usecase/controller/guard/interceptor/pipe/filter, mock dependencies, review test quality or coverage, run unit tests, debug failing or flaky tests, or optimize test performance and open handles.
---
# Unit Testing Skill
Unit testing validates individual functions, methods, and classes in isolation by mocking all external dependencies.
---
## Workflows
For guided, step-by-step execution of unit testing tasks, use the appropriate workflow:
| Workflow | Purpose | When to Use |
|----------|---------|-------------|
| [Setup](workflows/setup/workflow.md) | Initialize test infrastructure | New project or missing test setup |
| [Writing](workflows/writing/workflow.md) | Write new unit tests | Creating tests for components |
| [Reviewing](workflows/reviewing/workflow.md) | Review existing tests | Code review, quality audit |
| [Running](workflows/running/workflow.md) | Execute tests | Running tests, analyzing results |
| [Debugging](workflows/debugging/workflow.md) | Fix failing tests | Tests failing, need diagnosis |
| [Optimizing](workflows/optimizing/workflow.md) | Improve test performance | Slow tests, maintainability |
## Workflow Selection Guide
**IMPORTANT**: Before starting any testing task, identify the user's intent and load the appropriate workflow.
### Detect User Intent → Select Workflow
| User Says / Wants | Workflow to Load | File |
|-------------------|------------------|------|
| "Set up tests", "configure Jest", "add testing to project", "install test dependencies" | **Setup** | `workflows/setup/workflow.md` |
| "Write tests", "add tests", "create tests", "test this service/controller" | **Writing** | `workflows/writing/workflow.md` |
| "Review tests", "check test quality", "audit tests", "are these tests good?" | **Reviewing** | `workflows/reviewing/workflow.md` |
| "Run tests", "execute tests", "check if tests pass", "show test results" | **Running** | `workflows/running/workflow.md` |
| "Fix tests", "debug tests", "tests are failing", "why is this test broken?" | **Debugging** | `workflows/debugging/workflow.md` |
| "Speed up tests", "optimize tests", "tests are slow", "fix open handles" | **Optimizing** | `workflows/optimizing/workflow.md` |
### Workflow Execution Protocol
1. **ALWAYS load the workflow file first** - Read the full workflow before taking action
2. **Follow each step in order** - Complete checkpoints before proceeding
3. **Load knowledge files as directed** - Each workflow specifies which `references/` files to read
4. **Verify compliance after completion** - Re-read relevant reference files to ensure quality
---
## Knowledge Base Structure
```
references/
├── common/ # Core testing fundamentals
│ ├── knowledge.md # Testing philosophy and test pyramid
│ ├── rules.md # Mandatory testing rules (AAA, naming, coverage)
│ ├── assertions.md # Assertion patterns and matchers
│ ├── examples.md # Comprehensive examples by category
│ ├── detect-open-handles.md # Open handle detection and cleanup
│ └── performance-optimization.md # Jest runtime optimization
│
├── nestjs/ # NestJS component testing
│ ├── services.md # Service/usecase testing patterns
│ ├── controllers.md # Controller testing patterns
│ ├── guards.md # Guard testing patterns
│ ├── interceptors.md # Interceptor testing patterns
│ └── pipes-filters.md # Pipe and filter testing
│
├── mocking/ # Mock patterns and strategies
│ ├── deep-mocked.md # @golevelup/ts-jest patterns
│ ├── jest-native.md # Jest.fn, spyOn, mock patterns
│ └── factories.md # Test data factory patterns
│
├── repository/ # Repository testing
│ ├── mongodb.md # mongodb-memory-server patterns
│ └── postgres.md # pg-mem patterns
│
├── kafka/ # NestJS Kafka microservices testing
│ └── kafka.md # ClientKafka, @MessagePattern, @EventPattern handlers
│
└── redis/ # Redis cache testing
└── redis.md # Cache operations, health checks, graceful degradation
```
## Quick Reference by Task
### Write Unit Tests
1. **MANDATORY**: Read `references/common/rules.md` - AAA pattern, naming, coverage
2. Read `references/common/assertions.md` - Assertion best practices
3. Read component-specific files:
- **Services**: `references/nestjs/services.md`
- **Controllers**: `references/nestjs/controllers.md`
- **Guards**: `references/nestjs/guards.md`
- **Interceptors**: `references/nestjs/interceptors.md`
- **Pipes/Filters**: `references/nestjs/pipes-filters.md`
### Setup Mocking
1. Read `references/mocking/deep-mocked.md` - DeepMocked patterns
2. Read `references/mocking/jest-native.md` - Native Jest patterns
3. Read `references/mocking/factories.md` - Test data factories
### Test Repositories
1. **MongoDB**: `references/repository/mongodb.md`
2. **PostgreSQL**: `references/repository/postgres.md`
### Test Kafka (NestJS Microservices)
- Read `references/kafka/kafka.md` - ClientKafka mocking, @MessagePattern/@EventPattern handlers, emit/send testing
### Test Redis
- Read `references/redis/redis.md` - Cache operations, health checks, graceful degradation
### Examples
- Read `references/common/examples.md` for comprehensive patterns
### Optimize Test Performance
1. Read `references/common/performance-optimization.md` - Worker config, caching, CI optimization
2. Read `references/common/detect-open-handles.md` - Fix open handles preventing clean exit
### Debug Open Handles
- Read `references/common/detect-open-handles.md` - Detection commands, common handle types, cleanup patterns
---
## Core Principles
### 0. Context Efficiency (Temp File Output)
**ALWAYS redirect unit test output to temp files, NOT console**. Test output can be verbose and bloats agent context.
**IMPORTANT**: Use unique session ID in filenames to prevent conflicts when multiple agents run.
```bash
# Initialize session (once at start of testing session)
export UT_SESSION=$(date +%s)-$$
# Standard pattern - redirect output to temp file (NO console output)
npm test > /tmp/ut-${UT_SESSION}-output.log 2>&1
# Read summary only (last 50 lines)
tail -50 /tmp/ut-${UT_SESSION}-output.log
# Get failure details
grep -B 2 -A 15 "FAIL\|✕" /tmp/ut-${UT_SESSION}-output.log
# Cleanup when done
rm -f /tmp/ut-${UT_SESSION}-*.log /tmp/ut-${UT_SESSION}-*.md
```
**Temp Files** (with `${UT_SESSION}` unique per agent):
- `/tmp/ut-${UT_SESSION}-output.log` - Full test output
- `/tmp/ut-${UT_SESSION}-failures.md` - Tracking file for one-by-one fixing
- `/tmp/ut-${UT_SESSION}-debug.log` - Debug runs
- `/tmp/ut-${UT_SESSION}-verify.log` - Verification runs
- `/tmp/ut-${UT_SESSION}-coverage.log` - Coverage output
### 1. AAA Pattern (Mandatory)
ALL unit tests MUST follow Arrange-Act-Assert:
```typescript
it('should return user when found', async () => {
// Arrange
const userId = 'user-123';
mockRepository.findById.mockResolvedValue({
id: userId,
email: 'test@example.com',
name: 'Test User',
});
// Act
const result = await target.getUser(userId);
// Assert
expect(result).toEqual({
id: userId,
email: 'test@example.com',
name: 'Test User',
});
expect(mockRepository.findById).toHaveBeenCalledWith(userId);
});
```
### 2. Use `target` for SUT
Always name the system under test as `target`:
```typescript
let target: UserService;
let mockRepository: DeepMocked<UserRepository>;
```
### 3. DeepMocked Pattern
Use `@golevelup/ts-jest` for type-safe mocks:
```typescript
import { createMock, DeepMocked } from '@golevelup/ts-jest';
let mockService: DeepMocked<UserService>;
beforeEach(() => {
mockService = createMock<UserService>();
});
```
### 4. Specific Assertions
Assert exact values, not just existence:
```typescript
// WRONG
expect(result).toBeDefined();
expect(result.id).toBeDefined();
// CORRECT
expect(result).toEqual({
id: 'user-123',
email: 'test@example.com',
name: 'Test User',
});
```
### 5. Mock All Dependencies
Mock external services, never real databases for unit tests:
```typescript
// Unit Test: Mock repository
{ provide: UserRepository, useValue: mockRepository }
// Repository Test: Use in-memory database
const mongoServer = await createMongoMemoryServer();
```
---
## Standard Test Template
```typescript
import { Test, TestingModule } from '@nestjs/testing';
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { MockLoggerService } from 'src/shared/logger/services/mock-logger.service';
describe('UserService', () => {
let target: UserService;
let mockRepository: DeepMocked<UserRepository>;
beforeEach(async () => {
// Arrange: Create mocks
mockRepository = createMock<UserRepository>();
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
{ provide: UserRepository, useValue: mockRepository },
],
})
.setLogger(new MockLoggerService())
.compile();
target = module.get<UserService>(UserService);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('getUser', () => {
it('should return user when found', async () => {
// Arrange
mockRepository.findById.mockResolvedValue({
id: 'user-123',
email: 'test@example.com',
});
// Act
const result = await target.getUser('user-123');
// Assert
expect(result).toEqual({ id: 'user-123', email: 'test@example.com' });
});
it('should throw NotFoundException when user not found', async () => {
// Arrange
mockRepository.findById.mockResolvedValue(null);
// Act & Assert
await expect(target.getUser('invalid')).rejects.toThrow(NotFoundException);
});
});
});
```
---
## Test Coverage Requirements
| Category | Priority | Description |
|----------|----------|-------------|
| Happy path | MANDATORY | Valid inputs producing expected outputs |
| Edge cases | MANDATORY | Empty arrays, null values, boundaries |
| Error cases | MANDATORY | Not found, validation failures |
| Exception behavior | MANDATORY | Correct type, error code, message |
| Business rules | MANDATORY | Domain logic, calculations |
| Input validation | MANDATORY | Invalid inputs, type mismatches |
**Coverage Target:** 80%+ for new code
---
## Failure Resolution Protocol
**CRITICAL: Fix ONE test at a time. NEVER run full suite repeatedly while fixing.**
When unit tests fail:
1. **Initialize session** (once at start):
```bash
export UT_SESSION=$(date +%s)-$$
```
2. **Create tracking file**: `/tmp/ut-${UT_SESSION}-failures.md` with all failing tests
3. **Select ONE failing test** - work on only this test
4. **Run ONLY that test** (never full suite):
```bash
npm test -- -t "test name" > /tmp/ut-${UT_SESSION}-debug.log 2>&1
tail -50 /tmp/ut-${UT_SESSION}-debug.log
```
5. **Fix the issue** - analyze error, make targeted fix
6. **Verify fix** - run same test 3-5 times:
```bash
for i in {1..5}; do npm test -- -t "test name" > /tmp/ut-${UT_SESSION}-run$i.log 2>&1 && echo "Run $i: PASS" || echo "Run $i: FAIL"; done
```
7. **Mark as FIXED** in tracking file
8. **Move to next failing test** - repeat steps 3-7
9. **Run full suite ONLY ONCE** after ALL individual tests pass
10. **Cleanup**: `rm -f /tmp/ut-${UT_SESSION}-*.log /tmp/ut-${UT_SESSION}-*.md`
**WHY**: Running full suite wastes time and context. Each failing test pollutes output, making debugging harder.
---
## Naming Conventions
### Test Files
- Pattern: `*.spec.ts`
- Location: Co-located with source file
### Test Structure
```typescript
describe('ClassName', () => {
describe('methodName', () => {
it('should [expected behavior] when [condition]', () => {});
});
});
```
### Variable Names
| Variable | Convention |
|----------|------------|
| SUT | `target` |
| Mocks | `mock` prefix (`mockRepository`, `mockService`) |
| Mock Type | `DeepMocked<T>` |
---
## What NOT to Unit Test
Do NOT create unit tests for:
- **Interfaces** - Type definitions only, no runtime behavior
- **Enums** - Static value mappings, no logic to test
- **Constants** - Static values, no behavior
- **Type aliases** - Type definitions only
- **Plain DTOs** - Data structures without logic
Only test files containing **executable logic** (classes with methods, functions with behavior).
---
## Anti-Patterns to Avoid
| Don't | Why | Do Instead |
|-------|-----|------------|
| Assert only existence | Doesn't catch wrong values | Assert specific values |
| Conditional assertions | Non-deterministic | Separate test cases |
| Test private methods | Couples to implementation | Test via public interface |
| Share state between tests | Causes flaky tests | Fresh setup in beforeEach |
| Mock repositories in services | Tests implementation | Mock interfaces |
| Skip mock verification | Doesn't validate behavior | Verify mock calls |
| Test interfaces/enums/constants | No behavior to test | Skip these files |
---
## Checklist
**Setup:**
- [ ] Use `target` for system under test
- [ ] Use `mock` prefix for all mocks
- [ ] Use `DeepMocked<T>` type
- [ ] Include `.setLogger(new MockLoggerService())`
- [ ] Follow AAA pattern with comments
- [ ] Reset mocks in `afterEach`
**Coverage:**
- [ ] Happy path tests for all public methods
- [ ] Edge case tests (empty, null, boundaries)
- [ ] Error case tests (not found, validation failures)
- [ ] Exception type and error code verification
- [ ] Mock call verification (parameters + count)
**Quality:**
- [ ] 80%+ coverage on new code
- [ ] No assertions on log calls
- [ ] No test interdependence
- [ ] Tests fail when any field differs
