TypeScript Unit Testing
Unit testing practices in TypeScript projects to ensure code quality.
Unit tests verify code correctness. TypeScript's type system combines perfectly with testing frameworks, allowing you to write type-safe test code.
SVG Diagram: Unit Testing Workflow
Why Unit Testing Is Needed
Unit testing is an important means of ensuring code quality. It verifies code correctness and prevents bugs.
In TypeScript projects, test code also benefits from the type system: type errors are caught at compile time, the IDE provides intelligent suggestions, and test code is more reliable.
Moreover, test code is also the best documentation; you can understand the expected behavior of functions/classes through tests.
Quality Assurance:Unit tests quickly identify regression issues, ensuring code changes do not break existing functionality.
Test Framework Configuration
Jest is the most popular testing framework for TypeScript projects.
Install Jest
# - ts-jest:Enable Jest to run TypeScript
# - @types/jest:Type definitions for Jest
npm install --save-dev jest ts-jest @types/jest
# Initialize Jest configuration
npx ts-jest config:init
ts-jest:This is a preprocessor that lets Jest run TypeScript files directly without manual compilation.
Configure jest.config.js
Configure the Jest test environment.
jest.config.js
// Use the ts-jest preset
preset: 'ts-jest',
// Test environment: node or browser
testEnvironment: 'node',
// Test file directory
roots: ['<rootDir>/src'],
// Test file matching pattern
testMatch: ['**/__tests__/**/*.ts'],
// Supported file extensions
moduleFileExtensions: ['ts', 'js', 'json'],
// Files for collecting coverage
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts' // Exclude type declaration files
]
}
Configuration Notes:Test files are usually placed in the __tests__ directory or end with .test.ts.
Test Functions
First, write the business code that needs to be tested.
src/utils/calculator.ts
export class Calculator {
// Addition
add(a: number, b: number): number {
return a + b;
}
// Subtraction
subtract(a: number, b: number): number {
return a - b;
}
// Multiplication
multiply(a: number, b: number): number {
return a * b;
}
// Division
divide(a: number, b: number): number {
if (b === 0) {
throw new Error("Cannot divide by zero");
}
return a / b;
}
}
Then write the corresponding test code.
src/utils/calculator.test.ts
// Test suite: Tests for the Calculator class
describe("Calculator", () => {
let calculator: Calculator;
// Create a new Calculator instance before each test
beforeEach(() => {
calculator = new Calculator();
});
// Addition test
describe("add", () => {
it("should add two numbers", () => {
expect(calculator.add(2, 3)).toBe(5);
});
it("should handle negative numbers", () => {
expect(calculator.add(-1, 1)).toBe(0);
});
});
// Division test
describe("divide", () => {
it("should divide two numbers", () => {
expect(calculator.divide(10, 2)).toBe(5);
});
it("should throw error when dividing by zero", () => {
// Expect an error to be thrown
expect(() => calculator.divide(10, 0)).toThrow();
});
});
});
Run Result:
Calculator
add
✓ should add two numbers
✓ should handle negative numbers
divide
✓ should divide two numbers
✓ should throw error when dividing by zero
describe/it:describe is used to group tests, and it (or test) is used to define individual test cases.
Test Service
Test the business logic of the Service layer.
src/services/userService.ts
export interface User {
id: number;
name: string;
}
// User service class
export class UserService {
private users: User[] = [];
private nextId = 1;
// Create user
createUser(name: string): User {
const user = { id: this.nextId++, name };
this.users.push(user);
return user;
}
// Get user
getUser(id: number): User | undefined {
return this.users.find(u => u.id === id);
}
// Get all users
getAllUsers(): User[] {
return [...this.users];
}
}
src/services/userService.test.ts
describe("UserService", () => {
let service: UserService;
beforeEach(() => {
service = new UserService();
});
describe("createUser", () => {
it("should create a user with id", () => {
const user = service.createUser("Alice");
expect(user.id).toBe(1);
expect(user.name).toBe("Alice");
});
it("should increment id for each user", () => {
const user1 = service.createUser("Alice");
const user2 = service.createUser("Bob");
expect(user2.id).toBe(user1.id + 1);
});
});
describe("getUser", () => {
it("should return user by id", () => {
const created = service.createUser("Alice");
const found = service.getUser(created.id);
// Use optional chaining and toBe
expect(found?.name).toBe("Alice");
});
it("should return undefined for non-existent id", () => {
const found = service.getUser(999);
expect(found).toBeUndefined();
});
});
});
Run Result:
UserService
createUser
✓ should create a user with id
✓ should increment id for each user
getUser
✓ should return user by id
✓ should return undefined for non-existent id
Test Isolation:Each test case should be independent; use beforeEach to ensure each test has a clean state.
Mock
Use Mocks to simulate dependencies, such as external APIs, databases, etc.
Example
const mockCallback = jest.fn(x => x * 2);
// Use the mock function
[1, 2, 3].forEach(mockCallback);
// Verify the function was called 3 times
expect(mockCallback).toHaveBeenCalledTimes(3);
// Verify the arguments when the function was called
expect(mockCallback).toHaveBeenCalledWith(2);
// Mock module: Mock an entire module
jest.mock("./api", () => ({
fetchUser: jest.fn(() => Promise.resolve({ id: 1, name: "Alice" }))
}));
Run Result:
✓ mock 函数被调用 3 次
Purpose of Mocks:When the code under test depends on external systems, using Mocks isolates dependencies so that only the target code's logic is tested.
Notes
- Test file location:Place in the __tests__ directory or use the .test.ts suffix
- Test naming:Use descriptive test names that state the expected behavior
- Independent tests:Each test should run independently without relying on other tests
- Coverage:Focus on test coverage of core business logic
Best Practices:Tests should be fast, reliable, and independent of each other. Follow the AAA principle: Arrange (prepare), Act (execute), Assert (verify).
Summary
Unit testing is an important means of ensuring TypeScript code quality.
- Jest:The most popular TypeScript testing framework
- describe:Used to group related tests
- it/test:Define individual test cases
- expect:Assert test results
- Mock:Mock external dependencies
Other ExtensionsRecommendations:Write tests for critical business logic to ensure code changes do not introduce bugs.