Jest Setup
Jest Setup with TypeScript
Installation
npm install --save-dev jest @types/jest ts-jest
jest.config.ts
import type { Config } from 'jest';
const config: Config = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: ['**/*.test.ts', '**/*.spec.ts'],
moduleFileExtensions: ['ts', 'js', 'json'],
collectCoverage: true,
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov'],
clearMocks: true,
restoreMocks: true
};
export default config;
Basic Test
// math.ts
export function add(a: number, b: number): number {
return a + b;
}
// math.test.ts
import { add } from './math';
describe('add', () => {
it('should add two positive numbers', () => {
expect(add(2, 3)).toBe(5);
});
it('should handle negative numbers', () => {
expect(add(-1, -2)).toBe(-3);
});
it('should handle zero', () => {
expect(add(0, 5)).toBe(5);
});
});
Type-Safe Assertions
Type-Safe Assertions
Common Matchers
interface User {
id: number;
name: string;
email: string;
age?: number;
}
function createUser(name: string, email: string): User {
return { id: Date.now(), name, email };
}
describe('createUser', () => {
it('should create user with required fields', () => {
const user = createUser('Alice', 'alice@test.com');
expect(user).toEqual({
id: expect.any(Number),
name: 'Alice',
email: 'alice@test.com'
});
});
it('should have correct types', () => {
const user = createUser('Alice', 'alice@test.com');
expect(typeof user.id).toBe('number');
expect(typeof user.name).toBe('string');
});
it('should match object shape', () => {
const user = createUser('Alice', 'alice@test.com');
expect(user).toMatchObject({
name: expect.stringContaining('Ali'),
email: expect.stringMatching(/@test\.com$/)
});
});
});
Type Guards in Tests
type Result<T> = { success: true; data: T } | { success: false; error: string };
function isSuccess<T>(result: Result<T>): result is { success: true; data: T } {
return result.success;
}
describe('Result type', () => {
it('should narrow success result', () => {
const result: Result<number> = { success: true, data: 42 };
expect(isSuccess(result)).toBe(true);
if (isSuccess(result)) {
expect(result.data).toBe(42);
}
});
});
Mocking with TypeScript
Mocking with TypeScript
Mock Functions
type FetchFn = (url: string) => Promise<Response>;
function processUser(fetch: FetchFn, id: string): Promise<string> {
return fetch(`/api/users/${id}`)
.then(res => res.json())
.then(data => data.name);
}
describe('processUser', () => {
it('should fetch and process user name', async () => {
const mockFetch = jest.fn<FetchFn>().mockResolvedValue({
json: () => Promise.resolve({ name: 'Alice' })
} as Response);
const name = await processUser(mockFetch, '123');
expect(name).toBe('Alice');
expect(mockFetch).toHaveBeenCalledWith('/api/users/123');
});
});
Module Mocking
// UserService uses Database
jest.mock('./database.js');
import { db } from './database.js';
const mockDb = jest.mocked(db);
describe('UserService', () => {
it('should find user by id', async () => {
mockDb.query.mockResolvedValueOnce([{ id: '1', name: 'Alice' }]);
const user = await userService.findById('1');
expect(user).toEqual({ id: '1', name: 'Alice' });
expect(mockDb.query).toHaveBeenCalledWith(
'SELECT * FROM users WHERE id = ?',
['1']
);
});
});
Spy on Methods
class Logger {
log(msg: string) { console.log(msg); }
error(msg: string) { console.error(msg); }
}
describe('Logger', () => {
it('should log messages', () => {
const logger = new Logger();
const spy = jest.spyOn(logger, 'log');
logger.log('hello');
expect(spy).toHaveBeenCalledWith('hello');
expect(spy).toHaveBeenCalledTimes(1);
});
});
Async Testing
Async Testing
Testing Promises
function fetchData(url: string): Promise<{ data: string }> {
return fetch(url).then(res => res.json());
}
describe('fetchData', () => {
it('should return data on success', async () => {
global.fetch = jest.fn().mockResolvedValue({
json: () => Promise.resolve({ data: 'hello' })
});
const result = await fetchData('/api');
expect(result).toEqual({ data: 'hello' });
});
it('should reject on network error', async () => {
global.fetch = jest.fn().mockRejectedValue(new Error('Network error'));
await expect(fetchData('/api')).rejects.toThrow('Network error');
});
});
Testing with beforeEach/afterEach
describe('UserService', () => {
let service: UserService;
beforeEach(() => {
service = new UserService();
jest.clearAllMocks();
});
afterEach(() => {
jest.restoreAllMocks();
});
it('should create user', async () => {
const user = await service.create({ name: 'Alice' });
expect(user.id).toBeDefined();
expect(user.name).toBe('Alice');
});
});
Coverage Configuration
// package.json
{
"jest": {
"coverageThreshold": {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
}
}
}
Best Practices
- Use
jest.fn<T>()for typed mock functions - Use
jest.mocked()to type mocked modules - Test behavior, not implementation details
- Keep tests isolated with proper setup/teardown
- Use
describeblocks to organize related tests