From bmad-skills
Provides unit testing workflows for TypeScript/NestJS projects using Jest, ts-jest DeepMocked/createMock, in-memory DBs, and AAA pattern. Covers setup, writing/reviewing/running/debugging/optimizing .spec.ts tests.
npx claudepluginhub bmad-labs/skills --plugin bmad-skillsThis skill uses the workspace's default tool permissions.
Unit testing validates individual functions, methods, and classes in isolation by mocking all external dependencies.
LICENSEreferences/common/assertions.mdreferences/common/detect-open-handles.mdreferences/common/examples.mdreferences/common/knowledge.mdreferences/common/performance-optimization.mdreferences/common/rules.mdreferences/kafka/kafka.mdreferences/mocking/deep-mocked.mdreferences/mocking/factories.mdreferences/mocking/jest-native.mdreferences/nestjs/controllers.mdreferences/nestjs/guards.mdreferences/nestjs/interceptors.mdreferences/nestjs/pipes-filters.mdreferences/nestjs/services.mdreferences/redis/redis.mdreferences/repository/mongodb.mdreferences/repository/postgres.mdworkflows/debugging/steps/step-01-init.mdSuggests manual /compact at logical task boundaries in long Claude Code sessions and multi-phase tasks to avoid arbitrary auto-compaction losses.
Share bugs, ideas, or general feedback.
Unit testing validates individual functions, methods, and classes in isolation by mocking all external dependencies.
For guided, step-by-step execution of unit testing tasks, use the appropriate workflow:
| Workflow | Purpose | When to Use |
|---|---|---|
| Setup | Initialize test infrastructure | New project or missing test setup |
| Writing | Write new unit tests | Creating tests for components |
| Reviewing | Review existing tests | Code review, quality audit |
| Running | Execute tests | Running tests, analyzing results |
| Debugging | Fix failing tests | Tests failing, need diagnosis |
| Optimizing | Improve test performance | Slow tests, maintainability |
IMPORTANT: Before starting any testing task, identify the user's intent and load the appropriate 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 |
references/ files to readreferences/
├── 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
references/common/rules.md - AAA pattern, naming, coveragereferences/common/assertions.md - Assertion best practicesreferences/nestjs/services.mdreferences/nestjs/controllers.mdreferences/nestjs/guards.mdreferences/nestjs/interceptors.mdreferences/nestjs/pipes-filters.mdreferences/mocking/deep-mocked.md - DeepMocked patternsreferences/mocking/jest-native.md - Native Jest patternsreferences/mocking/factories.md - Test data factoriesreferences/repository/mongodb.mdreferences/repository/postgres.mdreferences/kafka/kafka.md - ClientKafka mocking, @MessagePattern/@EventPattern handlers, emit/send testingreferences/redis/redis.md - Cache operations, health checks, graceful degradationreferences/common/examples.md for comprehensive patternsreferences/common/performance-optimization.md - Worker config, caching, CI optimizationreferences/common/detect-open-handles.md - Fix open handles preventing clean exitreferences/common/detect-open-handles.md - Detection commands, common handle types, cleanup patternsALWAYS 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.
# 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 outputALL unit tests MUST follow Arrange-Act-Assert:
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);
});
target for SUTAlways name the system under test as target:
let target: UserService;
let mockRepository: DeepMocked<UserRepository>;
Use @golevelup/ts-jest for type-safe mocks:
import { createMock, DeepMocked } from '@golevelup/ts-jest';
let mockService: DeepMocked<UserService>;
beforeEach(() => {
mockService = createMock<UserService>();
});
Assert exact values, not just existence:
// WRONG
expect(result).toBeDefined();
expect(result.id).toBeDefined();
// CORRECT
expect(result).toEqual({
id: 'user-123',
email: 'test@example.com',
name: 'Test User',
});
Mock external services, never real databases for unit tests:
// Unit Test: Mock repository
{ provide: UserRepository, useValue: mockRepository }
// Repository Test: Use in-memory database
const mongoServer = await createMongoMemoryServer();
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);
});
});
});
| 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
CRITICAL: Fix ONE test at a time. NEVER run full suite repeatedly while fixing.
When unit tests fail:
export UT_SESSION=$(date +%s)-$$
/tmp/ut-${UT_SESSION}-failures.md with all failing testsnpm test -- -t "test name" > /tmp/ut-${UT_SESSION}-debug.log 2>&1
tail -50 /tmp/ut-${UT_SESSION}-debug.log
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
rm -f /tmp/ut-${UT_SESSION}-*.log /tmp/ut-${UT_SESSION}-*.mdWHY: Running full suite wastes time and context. Each failing test pollutes output, making debugging harder.
*.spec.tsdescribe('ClassName', () => {
describe('methodName', () => {
it('should [expected behavior] when [condition]', () => {});
});
});
| Variable | Convention |
|---|---|
| SUT | target |
| Mocks | mock prefix (mockRepository, mockService) |
| Mock Type | DeepMocked<T> |
Do NOT create unit tests for:
Only test files containing executable logic (classes with methods, functions with behavior).
| 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 |
Setup:
target for system under testmock prefix for all mocksDeepMocked<T> type.setLogger(new MockLoggerService())afterEachCoverage:
Quality: