Jest Configuration
Complete guide to Jest testing configuration in the TypeScript Project Template.
Overview
The template uses Jest with TypeScript support, providing:
- ES modules compatibility with ts-jest
- TypeScript compilation for test files
- Coverage reporting with configurable thresholds
- Async testing patterns for modern JavaScript
Configuration File (jest.config.js
)
Basic Structure
javascript
export default {
preset: 'ts-jest/presets/default-esm',
extensionsToTreatAsEsm: ['.ts'],
testEnvironment: 'node',
// TypeScript configuration
transform: {
'^.+\\.ts$': ['ts-jest', {
useESM: true,
tsconfig: {
module: 'ESNext'
}
}]
},
// Module resolution
moduleNameMapping: {
'^(\\.{1,2}/.*)\\.js$': '$1'
},
// Coverage configuration
collectCoverage: true,
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html'],
// Coverage thresholds
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
};
Key Configuration Options
TypeScript Integration
ES Modules Support
javascript
{
preset: 'ts-jest/presets/default-esm',
extensionsToTreatAsEsm: ['.ts'],
transform: {
'^.+\\.ts$': ['ts-jest', {
useESM: true
}]
}
}
Module Name Mapping
javascript
{
moduleNameMapping: {
// Map .js imports to .ts files
'^(\\.{1,2}/.*)\\.js$': '$1'
}
}
Coverage Configuration
Coverage Thresholds
javascript
{
coverageThreshold: {
global: {
branches: 80, // 80% branch coverage
functions: 80, // 80% function coverage
lines: 80, // 80% line coverage
statements: 80 // 80% statement coverage
}
}
}
Coverage Reporters
javascript
{
coverageReporters: [
'text', // Terminal output
'lcov', // For CI/CD integration
'html', // Browser-viewable report
'json' // Machine-readable format
]
}
Test Environment
Node.js Environment
javascript
{
testEnvironment: 'node',
setupFilesAfterEnv: ['<rootDir>/jest.setup.js']
}
Running Tests
Basic Commands
bash
# Run all tests
npm test
# Watch mode (auto-rerun on changes)
npm run test:watch
# Generate coverage report
npm run test:coverage
# CI mode (no watch, single run)
npm run test:ci
Advanced Test Commands
bash
# Run specific test file
npm test -- index.test.ts
# Run tests matching pattern
npm test -- --testNamePattern="should format"
# Debug mode
npm test -- --runInBand --detectOpenHandles
# Update snapshots
npm test -- --updateSnapshot
Test File Structure
Naming Conventions
__tests__/
├── index.test.ts # Tests for src/index.ts
├── utils.test.ts # Tests for src/utils.ts
└── helpers/
└── setup.test.ts # Tests for src/helpers/setup.ts
Test File Template
typescript
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
import { functionToTest } from '../src/module.js';
describe('functionToTest', () => {
beforeEach(() => {
// Setup before each test
});
afterEach(() => {
// Cleanup after each test
});
it('should handle normal input', () => {
const result = functionToTest('input');
expect(result).toBe('expected');
});
it('should handle edge cases', () => {
const result = functionToTest('');
expect(result).toBe('default');
});
it('should throw on invalid input', () => {
expect(() => functionToTest(null)).toThrow();
});
});
Async Testing
Promise-Based Testing
typescript
import { delay } from '../src/utils.js';
describe('delay', () => {
it('should resolve after specified time', async () => {
const startTime = Date.now();
await delay(100);
const endTime = Date.now();
expect(endTime - startTime).toBeGreaterThanOrEqual(90);
expect(endTime - startTime).toBeLessThan(150);
});
it('should handle zero delay', async () => {
await expect(delay(0)).resolves.toBeUndefined();
});
});
Error Testing
typescript
describe('safeJsonParse', () => {
it('should reject invalid JSON', () => {
const result = safeJsonParse('invalid json');
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error).toContain('Unexpected token');
}
});
});
Mocking
Function Mocking
typescript
import { jest } from '@jest/globals';
describe('with mocks', () => {
const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => {});
afterEach(() => {
mockConsoleLog.mockClear();
});
afterAll(() => {
mockConsoleLog.mockRestore();
});
it('should log message', () => {
functionThatLogs('test');
expect(mockConsoleLog).toHaveBeenCalledWith('test');
});
});
Module Mocking
typescript
// Mock entire module
jest.mock('../src/utils.js', () => ({
delay: jest.fn().mockResolvedValue(undefined),
formatTimestamp: jest.fn().mockReturnValue('mocked-timestamp')
}));
// Use mocked functions
import { delay } from '../src/utils.js';
const mockDelay = delay as jest.MockedFunction<typeof delay>;
Coverage Analysis
Coverage Reports
bash
# Generate coverage report
npm run test:coverage
# View HTML report
open coverage/lcov-report/index.html
# View text summary
cat coverage/lcov.info
Coverage Thresholds
javascript
{
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
},
// Per-file thresholds
'./src/critical.ts': {
branches: 95,
functions: 95,
lines: 95,
statements: 95
}
}
}
Excluding Files from Coverage
javascript
{
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/test-utils/**'
],
coveragePathIgnorePatterns: [
'/node_modules/',
'/dist/',
'/coverage/'
]
}
Debugging Tests
VS Code Integration
json
{
"type": "node",
"request": "launch",
"name": "Jest Tests",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": ["--runInBand"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}
Debug Specific Tests
bash
# Debug single test file
npm test -- --runInBand index.test.ts
# Debug with Node.js inspector
node --inspect-brk node_modules/.bin/jest --runInBand
# Verbose output
npm test -- --verbose
Advanced Configuration
Custom Test Environment
javascript
// jest.setup.js
import { jest } from '@jest/globals';
// Global test utilities
global.testUtils = {
wait: (ms) => new Promise(resolve => setTimeout(resolve, ms)),
generateTestData: (count) => Array.from({ length: count }, (_, i) => ({ id: i }))
};
// Mock global objects
global.fetch = jest.fn();
Performance Optimization
javascript
{
// Parallel execution
maxWorkers: '50%',
// Cache configuration
cache: true,
cacheDirectory: '<rootDir>/.jest-cache',
// Faster test discovery
testPathIgnorePatterns: [
'/node_modules/',
'/dist/',
'/coverage/'
]
}
Custom Matchers
javascript
// jest.setup.js
expect.extend({
toBeWithinRange(received, floor, ceiling) {
const pass = received >= floor && received <= ceiling;
if (pass) {
return {
message: () => `expected ${received} not to be within range ${floor} - ${ceiling}`,
pass: true,
};
} else {
return {
message: () => `expected ${received} to be within range ${floor} - ${ceiling}`,
pass: false,
};
}
},
});
Troubleshooting
Common Issues
ES Modules Errors
bash
# Error: Cannot use import statement outside a module
# Solution: Ensure proper ES modules configuration
{
"preset": "ts-jest/presets/default-esm",
"extensionsToTreatAsEsm": [".ts"]
}
TypeScript Compilation Errors
bash
# Error: TypeScript compilation failed
# Solution: Check tsconfig.json and Jest transform configuration
npm run build # Verify TypeScript compiles
Coverage Threshold Failures
bash
# Error: Coverage threshold not met
# Solution: Add more tests or adjust thresholds
npm run test:coverage # See detailed coverage report
Performance Issues
javascript
{
// Reduce parallel workers
maxWorkers: 1,
// Disable coverage for faster runs
collectCoverage: false,
// Use faster transform
transform: {
'^.+\\.ts$': 'ts-jest'
}
}
Best Practices
Test Organization
- Mirror source structure in test files
- Group related tests with describe blocks
- Use descriptive test names that explain the scenario
- Keep tests focused on single behaviors
Test Quality
- Test expected behavior first
- Include edge cases and error conditions
- Use appropriate assertions for the scenario
- Mock external dependencies appropriately
Maintenance
- Regular coverage review to identify gaps
- Update tests when code changes
- Refactor test utilities to reduce duplication
- Monitor test performance and optimize as needed