Giới thiệu tổng quan
Trong môi trường phát triển hiện đại, việc đưa test automation vào quy trình là yếu tố quyết định để duy trì chất lượng code, giảm thiểu lỗi runtime và tăng tốc độ triển khai. Đối với dự án Node.js viết bằng TypeScript, Jest kết hợp với ts-jest cung cấp một giải pháp toàn diện, hỗ trợ cả unit test, integration test và code coverage. Bài viết này sẽ đi sâu vào cách thiết lập môi trường, cấu hình, viết test mẫu, và tích hợp CI/CD, đồng thời chia sẻ các best practices mà các Senior Engineer thường áp dụng.
Cài đặt môi trường và cấu hình ban đầu
Bước 1: Khởi tạo dự án TypeScript
Giả sử bạn đã có một dự án Node.js với TypeScript. Nếu chưa, hãy tạo mới:
npm init -y npm install --save-dev typescript @types/node npx tsc --init
Trong file tsconfig.json, bật các tùy chọn sau để hỗ trợ Jest:
{
"compilerOptions": {
"target": "ES2019",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"sourceMap": true,
"outDir": "dist",
"rootDir": "src"
}
}
Bước 2: Cài đặt Jest và ts-jest
Thực hiện lệnh sau để cài đặt các package cần thiết:
npm install --save-dev jest ts-jest @types/jest
Sau khi cài đặt, khởi tạo cấu hình Jest:
npx ts-jest config:init
Lệnh trên sẽ tạo file jest.config.js với nội dung cơ bản:
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/__tests__/**/*.test.ts'],
collectCoverage: true,
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov'],
};
Điều quan trọng là preset: 'ts-jest' giúp Jest hiểu và biên dịch các file .ts trước khi thực thi.
Viết unit test cho các hàm thuần
Ví dụ 1: Hàm cộng đơn giản
File src/math.ts:
export function add(a: number, b: number): number {
return a + b;
}
File test src/__tests__/math.test.ts:
import { add } from '../math';
describe('add function', () => {
it('should return sum of two positive numbers', () => {
expect(add(2, 3)).toBe(5);
});
it('should handle negative numbers', () => {
expect(add(-2, -4)).toBe(-6);
});
});
Chạy npm test sẽ thấy kết quả:
> jest
PASS src/__tests__/math.test.ts
add function
✓ should return sum of two positive numbers (5 ms)
✓ should handle negative numbers (2 ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Ví dụ 2: Hàm generic với TypeScript
File src/utils.ts:
export function identity<T>(arg: T): T {
return arg;
}
File test src/__tests__/utils.test.ts:
import { identity } from '../utils';
describe('identity function', () => {
it('should return the same string', () => {
const result = identity('hello');
expect(result).toBe('hello');
});
it('should preserve object reference', () => {
const obj = { a: 1 };
const result = identity(obj);
expect(result).toBe(obj);
});
});
Những test này chứng minh rằng ts-jest có khả năng hiểu các kiểu generic mà không cần cấu hình thêm.
Mocking và test các module phụ thuộc
Mocking một service HTTP
Giả sử có một service thực hiện gọi API bằng axios:
import axios from 'axios';
export async function fetchUser(id: number) {
const response = await axios.get(`https://api.example.com/users/${id}`);
return response.data;
}
Để test mà không thực hiện request thực tế, ta sẽ mock axios:
import axios from 'axios';
import { fetchUser } from '../service';
jest.mock('axios');
const mockedAxios = axios as jest.Mocked;
describe('fetchUser', () => {
it('should return user data when API succeeds', async () => {
const mockData = { id: 1, name: 'Alice' };
mockedAxios.get.mockResolvedValue({ data: mockData });
const result = await fetchUser(1);
expect(result).toEqual(mockData);
expect(mockedAxios.get).toHaveBeenCalledWith('https://api.example.com/users/1');
});
});
Nhờ jest.mock và mockResolvedValue, chúng ta kiểm soát hoàn toàn hành vi của dependency, giảm thời gian chạy test và tránh phụ thuộc vào mạng.
Integration test cho API Node.js
Sử dụng SuperTest với Express
Giả sử có một server Express đơn giản:
import express from 'express';
const app = express();
app.use(express.json());
app.get('/status', (req, res) => {
res.json({ status: 'ok' });
});
export default app;
File test src/__tests__/api.test.ts:
import request from 'supertest';
import app from '../app';
describe('API integration tests', () => {
it('GET /status should return ok', async () => {
const response = await request(app).get('/status');
expect(response.status).toBe(200);
expect(response.body).toEqual({ status: 'ok' });
});
});
Để sử dụng supertest trong môi trường TypeScript, cần cài thêm:
npm install --save-dev supertest @types/supertest
Kết quả test sẽ chạy nhanh, không cần khởi động server thực tế, vì supertest tự tạo một instance của Express trong bộ nhớ.
Đánh giá độ bao phủ (code coverage) và tối ưu hoá
Jest tự động sinh báo cáo coverage khi bật collectCoverage: true. Chạy lệnh:
npm test -- --coverage
Kết quả sẽ xuất ra thư mục coverage với các file lcov.info và index.html. Mở coverage/index.html trong trình duyệt để xem chi tiết phần trăm bao phủ cho từng file.
Đối với các file có coverage thấp, hãy xem xét:
- Thêm test cho các nhánh
if/elsechưa được kiểm tra. - Sử dụng
jest.eachđể kiểm tra nhiều giá trị đầu vào. - Tránh viết code dead‑code hoặc quá phức tạp mà không có test.
Tích hợp CI/CD với GitHub Actions
Workflow cơ bản
File .github/workflows/ci.yml:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run tests with coverage
run: npm test -- --coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
Workflow này tự động cài đặt môi trường, chạy test và gửi báo cáo coverage lên Codecov, giúp đội ngũ luôn biết được mức độ an toàn của codebase.
Kết luận
Việc thiết lập Jest + ts-jest cho dự án TypeScript không chỉ mang lại khả năng kiểm thử mạnh mẽ mà còn giúp duy trì tiêu chuẩn code, giảm lỗi runtime và tăng tốc độ phát triển. Các kỹ thuật mocking, integration testing và coverage reporting được trình bày ở trên là những best practices mà các Senior Engineer thường áp dụng trong môi trường production. Khi kết hợp với CI/CD, quy trình kiểm thử trở thành một phần không thể thiếu của DevOps hiện đại. Tham khảo khóa học "Lập trình TypeScript từ cơ bản đến nâng cao" tại đây







