Facebook Pixel

Jest là gì? Hướng dẫn cấu hình Jest với Typescript

02 Sep, 2024

Tran Thuy Vy

Frontend Developer

Jest là một framework kiểm thử JavaScript tạo ra bởi Facebook, được thiết kế để làm cho việc viết kiểm thử trở nên dễ dàng

Jest là gì? Hướng dẫn cấu hình Jest với Typescript

Mục Lục

Bài viết này mình sẽ mang đến cho các bạn cái nhìn tổng quan về Jest, nếu các bạn chưa hiểu rõ về Unit Test thì nên đọc bài viết về Unit Test trước khi bắt đầu với Jest nhé.

1. Jest là gì?

Jest là một framework kiểm thử JavaScript tạo ra bởi Facebook, được thiết kế để làm cho việc viết kiểm thử trở nên dễ dàng. Ban đầu, Jest được tạo ra để kiểm thử các ứng dụng React, nhưng hiện nay được sử dụng để kiểm thử bất kỳ dự án Javascript nào.

2. TDD và BDD là gì?

Trước khi đi chi tiết vào Jest, thì mình muốn giới thiệu đến các phương pháp kiểm thử mà Jest hỗ trợ rất tốt: Test-Driven Development (TDD) và Behavior-Driven Development (BDD)

2.1 Test-Driven Development (TDD)

Test-Driven Development (TDD) là phương pháp phát triển phần mềm mà bài kiểm thử (tests) được viết trước khi viết code. TDD tập trung vào việc phát triển dựa trên yêu cầu về tính năng, và mỗi yêu cầu đó phải được kiểm thử cẩn thận để đảm bảo rằng nó được thực hiện đúng như mong đợi.

3 bước trong quy trình TDD được lặp đi lặp lại suốt quá trình phát triển phần mềm bao gồm:

  1. Red (Viết kiểm thử thất bại): bắt đầu bài kiểm thử không thành công (failing test). Bài kiểm thử này thể hiện yêu cầu mà mã cần thực hiện.
  2. Green (Viết mã để kiểm thử thành công): sau đó, viết mã tối thiểu cần thiết để bài kiểm thử thành công.
  3. Refactor (Cải tiến mã): cải tiến code, đảm bảo code sạch, tối ưu và vẫn giữ nguyên kết quả bài kiểm thử.
3 buoc viet TDD

2.2 Behavior-Driven Development (BDD)

Behavior-Driven Development (BDD) là mở rộng của TDD, tập trung vào việc phát triển phần mềm dựa trên các hành vi mong đợi của hệ thống.

Trong BDD, các bài kiểm thử được viết theo giai đoạn (scenarios):

  • Given: trạng thái ban đầu của hệ thống.
  • When: hành động hoặc sự kiện diễn ra.
  • Then: kết quả mong muốn sau khi hành động diễn ra.

Ví dụ với chức năng login cho bạn dễ hiểu hơn nhé:

  • Given: xác định người dùng đang ở trang đăng nhập.
  • When: xác định hành động mà người dùng thực hiện (nhập tên đăng nhập và mật khẩu hợp lệ).
  • Then: xác định kết quả mong muốn (người dùng được chuyển hướng đến trang chủ).
Các giai đoạn trong BDD

3. Ưu và Nhược điểm của Jest

3.1 Ưu điểm của Jest

  • Cấu hình đơn giản: jest không yêu cầu cấu hình phức tạp và có thể hoạt động ngay sau khi cài đặt (zero config).
  • Hiệu năng cao: jest hỗ trợ thực hiện kiểm thử song song, cải thiện hiệu suất, đặc biệt đối với các dự án lớn.
  • Snapshot Testing: đảm bảo không có sự thay đổi bất ngờ nào xảy ra.
  • Mocking: hỗ trợ tích hợp tốt cho việc mô phỏng các module và hàm, tách biệt các phần cần kiểm thử.
  • Code Coverage: tích hợp đo lường để đảm bảo rằng mã của bạn được kiểm thử đầy đủ.
  • Tích hợp với CI/CD: Dễ dàng tích hợp với Jenkins, GitHub Actions,...
Ưu điểm của Jest

3.2 Nhược điểm của Jest

  • Nếu không được tối ưu hoá có thể sẽ chậm hơn đối với các dự án rất lớn: chú ý trong việc cấu hình để đảm bảo các bài kiểm thử bị chạy lại không cần thiết.
  • Khả năng tương thích: mặc dù Jest hỗ trợ nhiều công cụ, nhưng đôi khi sẽ xảy ra xung đột với các thư viện khác, đặc biệt là khi bạn sử dụng các framework hoặc công cụ không phổ biến.
  • Snapshot Testing có thể gây phiền phức: bảo trì snapshot có thể trở nên phức tạp nếu bạn không được quản lý tốt.

4. Tổng quan về các tính năng của Jest

4.1 Using Matchers

Dùng để kiểm tra giá trị mong muốn với giá trị hàm thực hiện.

Typescript
test('two plus two is four', () => {
  expect(2 + 2).toBe(4);  // Kiểm tra phép tính 2 + 2 có bằng 4 không
});

test('object assignment', () => {
  const data: { one: number; two?: number } = { one: 1 };
  data['two'] = 2;
  expect(data).toEqual({ one: 1, two: 2 }); //Kiểm tra trường của object có giống nhau không
});

4.2 Test Truthiness (Kiểm tra tính đúng đắn)

Khi bạn cần phân biệt các giá trị false, null, undefined để có thể đưa ra cách xử lý phù hợp.

Typescript
test('null', () => {
  const n: null = null;
  expect(n).toBeNull();  // Kiểm tra biến n là null.
  expect(n).toBeDefined();  // Kiểm tra biến n được định nghĩa.
  expect(n).not.toBeUndefined();  // Kiểm tra biến n không phải undefined.
  expect(n).not.toBeTruthy();  // Kiểm tra biến n không phải là truthy.
  expect(n).toBeFalsy();  // Kiểm tra biến n là falsy.
});

4.3 Number

Dùng để so sánh các con số với các điều kiện như lớn hơn, nhỏ hơn, bằng nhau hoặc không bằng nhau.

Typescript
test('two plus two', () => {
  const value: number = 2 + 2;
  expect(value).toBeGreaterThan(3);  // Kiểm tra giá trị lớn hơn 3.
  expect(value).toBeLessThanOrEqual(4);  // Kiểm tra giá trị nhỏ hơn hoặc bằng 4.
  expect(value).toBe(4);  // Kiểm tra giá trị bằng 4.
});

4.4 String

Dùng để kiểm tra có hoặc không có trong chuỗi

Typescript
test('there is no I in team', () => {
  expect('team').not.toMatch(/I/);  // Kiểm tra chuỗi "team" không chứa ký tự "I" không.
});

4.5 Array

Dùng để kiểm tra xem mảng có chứa phần tử không

Typescript
test('the shopping list has beer on it', () => {
  const shoppingList: string[] = ['beer', 'milk', 'bread'];
  expect(shoppingList).toContain('beer');  // Kiểm tra mảng có chứa phần tử 'beer' không.
});

4.6 Exception

Dùng để kiểm tra xem function có ném ra exception không, nội dung của exception đó

Typescript
function compileAndroidCode(): never {
  throw new Error('200Lab');
}

test('compiling android goes as expected', () => {
  expect(() => compileAndroidCode()).toThrow('200Lab');  // Kiểm tra exception
});

4.7 Callbacks

Kiểm thử các function bất đồng bộ thông qua callbacks

Typescript
function fetchData(callback: (data: string) => void): void {
  callback('peanut butter');
}

test('the data is peanut butter', (done: jest.DoneCallback) => {
  function callback(data: string) {
    expect(data).toBe('peanut butter');
    done();  // Gọi `done` để kết thúc kiểm thử khi callback được gọi.
  }
  fetchData(callback);
});

4.8 Promise

Dùng để kiểm thử các function bất đồng bộ trả về Promise

Typescript
function fetchData(): Promise<string> {
  return Promise.resolve('peanut butter');
}

test('the data is peanut butter', () => {
  return fetchData().then(data => {
    expect(data).toBe('peanut butter');  // Kiểm tra dữ liệu trả về từ promise.
  });
});

4.9 Async/Await

Sử dụng cú pháp async / await để kiểm thử code bất đồng bộ

Typescript
async function fetchData(): Promise<string> {
  return 'peanut butter';
}

test('the data is peanut butter', async () => {
  const data = await fetchData();
  expect(data).toBe('peanut butter');  // Kiểm tra giá trị trả về từ hàm không đồng bộ.
});

4.10 Mock Function

Dùng để kiểm thử hàm và các giá trị trả về

Typescript
const myMock = jest.fn().mockReturnValue('default');  // Tạo một hàm mock với giá trị trả về mặc định.

test('mock function returns default value', () => {
  expect(myMock()).toBe('default');
});

4.11 Mock Implementations

Jest cho phép mô phỏng toàn bộ triển khai của một hàm

Typescript
const myMock = jest.fn((x: number, y: number) => x + y);  // Triển khai hàm mock tùy chỉnh.

test('mock implementation of addition', () => {
  expect(myMock(1, 2)).toBe(3);
});

4.12 Testing Snapshot

Giúp đảm bảo giao diện hoặc kết quả của function không thay đổi ngoài ý muốn

Typescript
test('snapshot', () => {
  const user = { name: 'John', age: 30 };
  expect(user).toMatchSnapshot();  // Tạo snapshot và so sánh
});

4.13 Tổ chức bài kiểm thử theo nhóm

Sử dụng describe để tổ chức các bài kiểm thử theo nhóm

Typescript
describe('matching cities to foods', () => {
  test('Vancouver has sushi', () => {
    expect(true).toBe(true);
  });

  test('Toronto has poutine', () => {
    expect(true).toBe(true);
  });
});

4.14 Thiết lập và dọn dẹp môi trường

Dùng beforeAllafterAll để thiết lập, dọn dẹp môi trường một lần cho tất cả các bài kiểm thử.

Typescript
beforeAll(() => {
  console.log('Setup before all tests');
});

afterAll(() => {
  console.log('Clean up after all tests');
});

Hoặc bạn cũng có thể sử dụng beforeEachafterEach đối với mỗi bài kiểm thử

Typescript
beforeEach(() => {
  console.log('Setup before each test');
});

afterEach(() => {
  console.log('Clean up after each test');
});

5. Hướng dẫn config Jest trong dự án Typescript

5.1 Khởi tạo dự án Typescript

Bash
npm init -y
npm install ts-node typescript --save-dev
npx tsc --init

Đây là file tsconfig.json

JSON
{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "exclude": ["node_modules"]
}

5.2 Cài đặt Jest

Bash
npm install jest @types/jest ts-jest --save-dev
  • jest: thư viện chính của Jest - trình chạy test
  • @types/jest: định nghĩa kiểu cho Jest
  • ts-jest: tích hợp Jest cho Typescript

5.3 Cấu hình Jest

Sau khi hoàn thành việc cài đặt các thư viện cần thiết, tiến hành tạo một tệp mới jest.config.ts ở thư mục gốc của dự án.

Typescript
import type { Config } from 'jest';

const config: Config = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  testMatch: ['**/*.test.ts'],
  moduleFileExtensions: ['js', 'ts'],
};

export default config;

Sau đó, tương tự với câu lệnh build bạn hãy vào lại file package.json để thêm câu lệnh "test":"jest" vào phần script nha.

JSON
{
  "name": "200lab_jest_demo",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.ts",
  "scripts": {
    "build": "tsc",
    "test": "jest"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/jest": "^29.5.12",
    "jest": "^29.7.0",
    "ts-jest": "^29.2.5",
    "ts-node": "^10.9.2",
    "typescript": "^5.5.4"
  }
}

5.4 Tạo mới Test case

Đầu tiên, bạn tiến hành tạo file src/math.test.ts và file src/math.ts.

  • src/math.ts
Typescript
export function add(a: number, b: number): number {
  return a + b;
}
  • src/math.test.ts
Typescript
import { add } from './math';

describe('Math functions', () => {
  test('should return 5 for add(2, 3)', () => {
    expect(add(2, 3)).toBe(5);
  });

  test('should return 0 for add(-2, 2)', () => {
    expect(add(-2, 2)).toBe(0);
  });
});

Cuối cùng, hãy chạy thử npm test và xem kết quả trên terminal

Test with Jest

6. Kết luận

Qua bài viết này, hy vọng bạn sẽ hiểu hơn về Jest, hiểu biết cơ bản về việc sử dụng Jest trong dự án Typescript của mình. Trong các dự án lớn, bạn có thể tích hợp thêm husky hoặc eslint.

Một vài chủ đề thú vị có thể bạn quan tâm:

Bài viết liên quan

Lập trình backend expressjs

xây dựng hệ thống microservices
  • Kiến trúc Hexagonal và ứng dụngal font-
  • TypeScript: OOP và nguyên lý SOLIDal font-
  • Event-Driven Architecture, Queue & PubSubal font-
  • Basic scalable System Designal font-

Đăng ký nhận thông báo

Đừng bỏ lỡ những bài viết thú vị từ 200Lab