Ts Unit Testing
Unit testing practices in TypeScript projects to ensure code quality.
Unit tests can verify the correctness of code. The TypeScript type system works perfectly with testing frameworks, allowing you to write type-safe test code.
* * *
* * *
## Why Unit Testing is Needed
Unit testing is an important means of ensuring code quality. It can verify the correctness of code and prevent bugs from occurring.
In TypeScript projects, test code also benefits from the type system: type errors are detected at compile time, IDEs provide intelligent hints, and test code becomes more reliable.
Additionally, test code serves as the best documentation β you can understand the expected behavior of functions/classes through tests.
> **Quality Assurance:** Unit tests can quickly identify regression issues, ensuring that code changes do not break existing functionality.
* * *
## Test Framework Configuration
Jest is the most popular testing framework for TypeScript projects.
## Installing Jest
# Install Jest and related dependencies
# - ts-jest: Allows Jest to run TypeScript files
# -@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 allows Jest to directly run TypeScript files without manual compilation.
* * *
## Configuring jest.config.js
Configure the Jest testing environment.
## jest.config.js
module.exports={
// Use ts-jest preset
preset:'ts-jest',
// Test environment: node or browser
testEnvironment:'node',
// Directory for test files
roots:['/src'],
// Pattern for matching test files
testMatch:['**/__tests__/**/*.ts'],
// Supported file extensions
moduleFileExtensions:['ts','js','json'],
// Files to collect coverage from
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.
* * *
## Testing Functions
First, write the business code to be tested.
## src/utils/calculator.ts
// Calculator class
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 corresponding test code.
## src/utils/calculator.test.ts
import{ Calculator } from "./calculator";
// Test suite: Tests for Calculator class
describe("Calculator",()=>{
let calculator: Calculator;
// Create a new Calculator instance before each test
beforeEach(()=>{
calculator =new Calculator();
});
// Addition tests
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 tests
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();
});
});
});
**Output:**
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, while it (or test) defines individual test cases.
* * *
## Testing Services
Testing business logic in the service layer.
## src/services/userService.ts
// User type
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
import{ UserService } from "./userService";
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);
// Using 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();
});
});
});
**Output:**
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.
* * *
## Mocking
Use Mock to simulate dependencies such as external APIs, databases, etc.
## Example
// Mock function: Create a mock function
const mockCallback = jest.fn(x => x *2);
// Use the mock function
[1,2,3].forEach(mockCallback);
// Verify function was called 3 times
expect(mockCallback).toHaveBeenCalledTimes(3);
// Verify arguments passed when function was called
expect(mockCallback).toHaveBeenCalledWith(2);
// Mock module: Simulate entire module
jest.mock("./api",()=>({
fetchUser: jest.fn(()=> Promise.resolve({ id:1, name:"Alice"}))
}));
**Output:**
β mock function called 3 times
> **Purpose of Mocking:** When the code being tested depends on external systems, using Mock isolates these dependencies so only the target code logic is tested.
* * *
## Considerations
* **Test File Location:** Place in __tests__ directory or use .test.ts suffix
* **Test Naming:** Use descriptive test names that explain expected behavior
* **Independent Tests:** Each test should run independently without relying on others
* **Coverage:** Focus on core business logic test coverage
> **Best Practices:** Tests should be fast, reliable, and independent. Follow the AAA principle: Arrange (Setup), Act (Execution), Assert (Assertion).
* * *
## Summary
Unit testing is an essential approach to ensure TypeScript code quality.
* **Jest:** The most popular TypeScript testing framework
* **describe:** Used to group related tests
* **it/test:** Defines individual test cases
* **expect:** Asserts test results
* **Mock:** Simulates external dependencies
> **Recommendation:** Write tests for key business logic to ensure that code changes don't introduce bugs.
YouTip