From bmad-skills
Guides writing and debugging TypeScript/NestJS unit tests with Jest, DeepMocked createMock, and in-memory databases. Activates on .spec.ts files or testing workflows.
How this skill is triggered — by the user, by Claude, or both
Slash command
/bmad-skills:typescript-unit-testingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
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.mdUnit 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: '[email protected]',
name: 'Test User',
});
// Act
const result = await target.getUser(userId);
// Assert
expect(result).toEqual({
id: userId,
email: '[email protected]',
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: '[email protected]',
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: '[email protected]',
});
// Act
const result = await target.getUser('user-123');
// Assert
expect(result).toEqual({ id: 'user-123', email: '[email protected]' });
});
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:
npx claudepluginhub bmad-labs/skills --plugin bmad-skillsWrites, reviews, and debugs Jest unit tests for TypeScript/JavaScript projects. Covers mocking, spies, snapshots, coverage, async testing, and custom matchers.
Generates test suites with unit, integration, and e2e tests, proper mocking strategies, and edge case coverage. Works with any language/framework.
Sets up, writes, reviews, runs, debugs, and optimizes E2E/integration tests for TypeScript/NestJS projects using Jest, supertest, and real Docker infrastructure (Kafka, PostgreSQL, MongoDB, Redis) with the Given-When-Then pattern.