Bài 20.1: Jest và React Testing Library trong TypeScript

Bài 20.1: Jest và React Testing Library trong TypeScript
Chào mừng bạn đến với bài viết chuyên sâu về _kiểm thử ứng dụng React_ của chúng ta! Khi xây dựng các ứng dụng web front-end ngày càng phức tạp với React và TypeScript, việc đảm bảo chất lượng và sự ổn định trở nên cực kỳ quan trọng. Đây là lúc kiểm thử tự động phát huy sức mạnh.
Trong bài viết này, chúng ta sẽ cùng nhau đi sâu vào hai công cụ quen thuộc và mạnh mẽ trong hệ sinh thái React để thực hiện kiểm thử: Jest và React Testing Library (RTL), đặc biệt là khi kết hợp với TypeScript để tăng cường độ tin cậy.
Tại sao phải kiểm thử ứng dụng React của bạn?
Bạn có thể tự hỏi: "Ứng dụng của tôi hoạt động tốt trên trình duyệt, vậy tại sao lại cần kiểm thử nữa?". Kiểm thử tự động mang lại những lợi ích vô giá:
- Bắt lỗi sớm: Phát hiện bug ngay trong quá trình phát triển thay vì để người dùng cuối tìm thấy.
- Tự tin khi refactor: Bạn có thể tự tin thay đổi cấu trúc code, tối ưu hóa mà không sợ làm hỏng chức năng hiện có vì các bài kiểm thử sẽ cảnh báo bạn nếu có gì đó sai.
- Đảm bảo hành vi người dùng: Kiểm thử đảm bảo rằng ứng dụng hoạt động đúng như mong đợi từ góc độ của người dùng.
- Codebase ổn định hơn: Giảm thiểu lỗi regression (lỗi xuất hiện lại sau khi sửa chữa).
- Tài liệu sống: Các bài kiểm thử có thể hoạt động như một dạng tài liệu cho thấy cách các component nên hoạt động.
Khi kết hợp với TypeScript, chúng ta còn có thêm một lớp bảo vệ tĩnh, giúp phát hiện lỗi liên quan đến kiểu dữ liệu ngay tại thời điểm compile, bổ sung hoàn hảo cho kiểm thử động của Jest và RTL.
Jest: Trái tim của việc kiểm thử JavaScript
Jest là một framework kiểm thử JavaScript được phát triển bởi Facebook (nay là Meta) và được sử dụng rộng rãi trong cộng đồng React. Jest không chỉ là một công cụ chạy kiểm thử (test runner) mà còn cung cấp các công cụ mạnh mẽ để viết bài kiểm thử, so khớp giá trị (assertions) và tạo mock dữ liệu.
Các thành phần chính của Jest bao gồm:
- Test Runner: Chạy các file kiểm thử của bạn.
- Assertion Library: Cung cấp các phương thức
expect
vàmatcher
(toBe
,toEqual
,toHaveBeenCalled
, v.v.) để kiểm tra kết quả. - Mocking: Cho phép thay thế các dependencies (hàm, module) bằng các phiên bản giả (mock) để kiểm thử code một cách cô lập.
- Code Coverage: Báo cáo mức độ code của bạn đã được kiểm thử.
Hãy xem một ví dụ đơn giản về cách sử dụng Jest để kiểm thử một hàm TypeScript thông thường (chưa liên quan đến React):
// src/utils.ts
export function sum(a: number, b: number): number {
return a + b;
}
export function isEven(num: number): boolean {
return num % 2 === 0;
}
// src/__tests__/utils.test.ts
import { sum, isEven } from '../utils';
// describe: Gom nhóm các bài kiểm thử liên quan
describe('Utility functions', () => {
// it hoặc test: Một bài kiểm thử cụ thể
it('should correctly sum two numbers', () => {
// expect: Giá trị cần kiểm tra
// toBe: Matcher so sánh giá trị
expect(sum(1, 2)).toBe(3);
expect(sum(-1, 5)).toBe(4);
});
it('should check if a number is even', () => {
expect(isEven(4)).toBe(true);
expect(isEven(7)).toBe(false);
expect(isEven(0)).toBe(true);
});
});
Giải thích code:
- Chúng ta import các hàm cần kiểm thử từ file
src/utils.ts
. describe('Utility functions', ...)
tạo ra một khối (suite) chứa các bài kiểm thử cho nhóm hàm tiện ích. Điều này giúp cấu trúc các bài kiểm thử của bạn gọn gàng hơn.it('should correctly sum two numbers', ...)
định nghĩa một bài kiểm thử cụ thể. Chuỗi đầu tiên mô tả chức năng đang được kiểm thử.test
cũng có chức năng tương tự nhưit
.expect(sum(1, 2))
lấy kết quả của hàmsum(1, 2)
..toBe(3)
là một matcher của Jest, dùng để so sánh kết quả trả về với giá trị mong đợi (ở đây là 3). Jest có rất nhiều matchers khác nhau nhưtoEqual
(so sánh giá trị của object/array),toBeTruthy
,toBeFalsy
,toContain
,toHaveLength
, v.v.
Để chạy bài kiểm thử này, bạn thường sử dụng lệnh jest
trong terminal (sau khi đã cài đặt và cấu hình Jest, bao gồm cả ts-jest
hoặc @swc/jest
để xử lý TypeScript).
React Testing Library (RTL): Kiểm thử như người dùng
Trong khi Jest là framework kiểm thử, React Testing Library là một thư viện cung cấp các tiện ích để kiểm thử các component React một cách hiệu quả. Triết lý cốt lõi của RTL rất đơn giản: kiểm thử code của bạn theo cách mà người dùng sử dụng ứng dụng của bạn.
Điều này có nghĩa là RTL khuyến khích bạn:
- Tìm kiếm các phần tử trên DOM dựa trên các thuộc tính mà người dùng nhìn thấy hoặc tương tác với (text, label, role, placeholder...).
- Kích hoạt các sự kiện giống như người dùng thật (click, type, blur...).
- Hạn chế kiểm thử trạng thái nội bộ của component hoặc các chi tiết triển khai không hiển thị ra ngoài DOM.
Tại sao triết lý này lại quan trọng?
Kiểm thử dựa trên chi tiết triển khai (như kiểm thử state nội bộ, gọi trực tiếp các phương thức của component instance) có xu hướng dễ bị hỏng ngay cả khi bạn chỉ thay đổi nhỏ trong cách component hoạt động bên trong mà không làm thay đổi giao diện hoặc hành vi đối với người dùng. Ngược lại, kiểm thử dựa trên hành vi người dùng sẽ mạnh mẽ hơn và ít bị ảnh hưởng bởi các thay đổi refactor nội bộ.
Các tiện ích chính của RTL bao gồm:
render
: Render component React vào một container DOM ảo.screen
: Một object cung cấp các phương thức query (truy vấn) để tìm kiếm các phần tử trong DOM đã render.- Queries: Các phương thức để tìm kiếm phần tử (
getBy
,queryBy
,findBy
+ biến thể theo Role, Text, LabelText, PlaceholderText, DisplayValue, TestId...). Nên ưu tiên tìm kiếm theoRole
vì nó gần nhất với cách công nghệ hỗ trợ (accessibility) và người dùng tương tác với các thành phần UI. fireEvent
/@testing-library/user-event
: Giả lập các sự kiện DOM. Thư viện@testing-library/user-event
được khuyến khích hơn vì nó mô phỏng hành vi người dùng thật hơn (ví dụ: gõ vào input không chỉ kích hoạtchange
mà còn cảkeyDown
,keyPress
,keyUp
).waitFor
: Chờ cho một điều kiện nào đó xảy ra trong DOM (hữu ích khi kiểm thử các hành động bất đồng bộ).@testing-library/jest-dom
: Cung cấp các custom matchers cho Jest để kiểm thử trạng thái của DOM một cách tiện lợi (toBeInTheDocument
,toBeVisible
,toHaveTextContent
, v.v.).
RTL hoạt động trên nền Jest. Jest là công cụ chạy bài kiểm thử và cung cấp các expect
/matcher
, còn RTL cung cấp các công cụ để render component React và tương tác/truy vấn trên DOM ảo đó để Jest có thể kiểm thử.
Thiết lập cơ bản Jest và RTL với TypeScript
Để sử dụng Jest và RTL với TypeScript trong dự án React của bạn, bạn cần cài đặt một số gói npm:
npm install --save-dev jest @types/jest ts-jest @testing-library/react @testing-library/jest-dom @types/testing-library__jest-dom @swc/jest
Hoặc nếu dùng yarn:
yarn add --dev jest @types/jest ts-jest @testing-library/react @testing-library/jest-dom @types/testing-library__jest-dom @swc/jest
jest
: Framework kiểm thử chính.@types/jest
: Định nghĩa kiểu TypeScript cho Jest.ts-jest
hoặc@swc/jest
: Bộ chuyển đổi (transformer) để Jest hiểu code TypeScript/TSX.@swc/jest
thường nhanh hơnts-jest
. Bạn chỉ cần chọn một trong hai.@testing-library/react
: Thư viện chính để kiểm thử React component.@testing-library/jest-dom
: Cung cấp các custom matchers hữu ích cho DOM.@types/testing-library__jest-dom
: Định nghĩa kiểu TypeScript cho@testing-library/jest-dom
.
Sau đó, bạn cần cấu hình Jest (thường trong package.json
hoặc file jest.config.js
/ jest.config.ts
). Cấu hình này sẽ chỉ cho Jest cách xử lý các file TypeScript/TSX và thiết lập môi trường DOM ảo. Bạn cũng cần thêm dòng import '@testing-library/jest-dom';
vào file setup của Jest (setupFilesAfterEnv
) để các custom matchers được sử dụng.
// jest.config.js hoặc jest.config.ts
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest', // Hoặc '@swc/jest' nếu dùng swc
testEnvironment: 'jsdom', // Mô phỏng môi trường trình duyệt (DOM)
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'], // Đường dẫn tới file setup (ví dụ: src/setupTests.ts)
};
// src/setupTests.ts
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
Viết bài kiểm thử React Component đầu tiên với TypeScript
Hãy cùng kiểm thử một component đơn giản: một nút bấm (Button
) hiển thị văn bản và gọi một hàm khi được click.
// src/components/Button.tsx
import React from 'react';
interface ButtonProps {
onClick: () => void;
children: React.ReactNode; // Hoặc string nếu chỉ chấp nhận văn bản
}
const Button: React.FC<ButtonProps> = ({ onClick, children }) => {
return (
<button type="button" onClick={onClick}>
{children}
</button>
);
};
export default Button;
Và đây là bài kiểm thử cho component đó:
// src/components/__tests__/Button.test.tsx
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; // Thư viện user-event
import Button from '../Button'; // Import component cần kiểm thử
// describe: Gom nhóm bài kiểm thử cho component Button
describe('Button Component', () => {
// Test case 1: Kiểm tra component render đúng nội dung văn bản
it('should render the correct text', () => {
const buttonText = 'Click me';
render(<Button onClick={() => {}}>{buttonText}</Button>);
// screen.getByText: Tìm kiếm phần tử dựa trên nội dung văn bản
// expect().toBeInTheDocument(): Matcher của @testing-library/jest-dom
const buttonElement = screen.getByText(buttonText);
expect(buttonElement).toBeInTheDocument();
});
// Test case 2: Kiểm tra hàm onClick được gọi khi nút bấm
it('should call the onClick handler when clicked', async () => {
// jest.fn(): Tạo một hàm mock để kiểm tra xem nó có được gọi hay không
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Test Button</Button>);
// screen.getByRole: Tìm kiếm phần tử dựa trên vai trò (role)
// 'button': Vai trò của thẻ <button>
const buttonElement = screen.getByRole('button', { name: /test button/i }); // Tìm nút có vai trò 'button' và tên (văn bản) là "Test Button" (regex không phân biệt hoa thường)
// userEvent.click: Mô phỏng hành động click của người dùng
await userEvent.click(buttonElement); // Sử dụng await vì userEvent trả về Promise
// expect().toHaveBeenCalledTimes(): Matcher kiểm tra số lần hàm mock được gọi
expect(handleClick).toHaveBeenCalledTimes(1);
});
});
Giải thích code kiểm thử:
- Chúng ta import
render
vàscreen
từ@testing-library/react
,userEvent
từ@testing-library/user-event
và componentButton
. describe('Button Component', ...)
tạo khối cho các bài kiểm thử củaButton
.- Test 1 (
should render the correct text
):render(<Button onClick={() => {}}>{buttonText}</Button>)
render componentButton
vào môi trường kiểm thử. Hàmrender
trả về một số tiện ích, nhưng thường chúng ta chỉ cần dùngscreen
.screen.getByText(buttonText)
tìm kiếm một phần tử trong DOM ảo có chứa văn bản khớp vớibuttonText
. Nếu không tìm thấy hoặc tìm thấy nhiều hơn một, nó sẽ throw lỗi.expect(buttonElement).toBeInTheDocument()
sử dụng matcher từ@testing-library/jest-dom
để xác nhận rằng phần tử tìm được thực sự tồn tại trong DOM.
- Test 2 (
should call the onClick handler when clicked
):const handleClick = jest.fn();
tạo ra một hàm mock rỗng bằng Jest. Mục đích là để chúng ta có thể theo dõi xem hàm này có được gọi hay không, được gọi bao nhiêu lần, với đối số nào, v.v.render(<Button onClick={handleClick}>Test Button</Button>);
render component, truyền hàm mock vào proponClick
.screen.getByRole('button', { name: /test button/i })
tìm kiếm phần tử có vai trò accessibility là 'button' và có tên (thường là văn bản nội dung) chứa chuỗi "test button" (không phân biệt hoa thường do cời
). Tìm kiếm theo role là cách tốt nhất vì nó mô phỏng cách người dùng và công nghệ hỗ trợ tương tác với UI.await userEvent.click(buttonElement);
sử dụng thư việnuser-event
để mô phỏng hành động click của người dùng vào phần tử nút bấm. Chúng ta dùngawait
vìuserEvent
thường thực hiện các hành động bất đồng bộ (chờ đợi như người dùng thật).expect(handleClick).toHaveBeenCalledTimes(1);
sử dụng matcher của Jest để kiểm tra xem hàm mockhandleClick
có được gọi chính xác 1 lần sau khi hành động click xảy ra hay không.
Vai trò của TypeScript trong bài kiểm thử này:
- Khi bạn viết
Button.tsx
, TypeScript đảm bảo rằng bạn định nghĩa và sử dụng props (onClick
,children
) đúng kiểu. - Trong file test
Button.test.tsx
, khi bạn import componentButton
, TypeScript biết các props mà nó yêu cầu. Nếu bạn quên truyền proponClick
hoặc truyền sai kiểu dữ liệu, TypeScript sẽ báo lỗi ngay tại thời điểm code, trước khi bạn chạy bài kiểm thử bằng Jest. - Các định nghĩa kiểu từ
@types/jest
và@types/testing-library__jest-dom
cung cấp type safety cho các hàm của Jest, RTL và các matchers, giúp bạn sử dụng chúng đúng cách và nhận được gợi ý từ IDE.
Kiểm thử các kịch bản phổ biến khác
Ngoài việc render và click đơn giản, RTL cho phép bạn kiểm thử nhiều kịch bản phức tạp hơn:
1. Kiểm thử Input và Thay đổi giá trị
Kiểm thử việc nhập liệu vào các trường input:
// src/components/__tests__/Input.test.tsx
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
interface InputProps {
value: string;
onChange: (newValue: string) => void;
label: string;
}
const Input: React.FC<InputProps> = ({ value, onChange, label }) => {
return (
<div>
<label htmlFor="my-input">{label}</label>
<input id="my-input" type="text" value={value} onChange={(e) => onChange(e.target.value)} />
</div>
);
};
describe('Input Component', () => {
it('should update value when user types', async () => {
const handleChange = jest.fn();
render(<Input value="" onChange={handleChange} label="Username" />);
// Tìm input bằng label text liên kết với nó
const inputElement = screen.getByLabelText('Username');
const testValue = 'john.doe';
// Mô phỏng gõ văn bản
await userEvent.type(inputElement, testValue);
// Kiểm tra xem hàm onChange có được gọi với giá trị đúng
// toHaveBeenCalledWith: Matcher kiểm tra đối số của hàm mock
expect(handleChange).toHaveBeenCalledWith(testValue);
});
});
Giải thích:
- Chúng ta sử dụng
screen.getByLabelText('Username')
để tìm phần tử<input>
được liên kết với<label>
có nội dung "Username". Đây là cách tìm kiếm thân thiện với accessibility. userEvent.type(inputElement, testValue)
mô phỏng hành động gõ chuỗitestValue
vào input.expect(handleChange).toHaveBeenCalledWith(testValue)
kiểm tra xem hàm mockhandleChange
đã được gọi với đối số chính xác là giá trị đã gõ hay chưa.
2. Kiểm thử các hành động bất đồng bộ (Async Operations)
Khi component của bạn fetch data hoặc thực hiện các hành động bất đồng bộ khác, bạn cần chờ đợi kết quả trước khi kiểm thử. RTL cung cấp các queries findBy*
và tiện ích waitFor
cho mục đích này.
// src/components/AsyncDataDisplay.tsx
import React, { useState, useEffect } from 'react';
interface Data {
id: number;
name: string;
}
// Hàm mock fetch data
const fetchData = async (): Promise<Data> => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: 1, name: 'Sample Data' });
}, 100); // Giả lập độ trễ 100ms
});
};
const AsyncDataDisplay: React.FC = () => {
const [data, setData] = useState<Data | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchData().then((result) => {
setData(result);
setLoading(false);
});
}, []);
if (loading) {
return <div>Loading...</div>;
}
if (!data) {
return <div>Error loading data.</div>;
}
return (
<div>
<h1>Data Loaded</h1>
<p data-testid="data-name">{data.name}</p> {/* Sử dụng data-testid làm fallback */}
</div>
);
};
export default AsyncDataDisplay;
// src/components/__tests__/AsyncDataDisplay.test.tsx
import React from 'react';
import { render, screen, waitFor } from '@testing-library/react'; // Import waitFor
import AsyncDataDisplay from '../AsyncDataDisplay';
// Mock hàm fetchData để kiểm soát kết quả trả về trong bài kiểm thử
// jest.mock('../AsyncDataDisplay', () => ({
// ...jest.requireActual('../AsyncDataDisplay'),
// fetchData: jest.fn(() =>
// Promise.resolve({ id: 1, name: 'Mocked Data' }) // Trả về dữ liệu giả
// ),
// }));
describe('AsyncDataDisplay Component', () => {
it('should display data after loading', async () => {
render(<AsyncDataDisplay />);
// screen.getByText: Tìm kiếm phần tử 'Loading...' ban đầu
expect(screen.getByText('Loading...')).toBeInTheDocument();
// screen.findByText: Async query, chờ đợi phần tử xuất hiện trong DOM
// Nó sẽ thử tìm kiếm lại cho đến khi timeout hoặc tìm thấy
const dataNameElement = await screen.findByText('Sample Data'); // Chờ 'Sample Data' xuất hiện
// Hoặc sử dụng findByRole nếu có vai trò phù hợp
// const dataHeader = await screen.findByRole('heading', { name: /data loaded/i });
// Sau khi phần tử dữ liệu đã xuất hiện, kiểm tra nó
expect(dataNameElement).toBeInTheDocument();
// Kiểm tra phần tử 'Loading...' đã biến mất
expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); // queryBy* trả về null nếu không tìm thấy
});
// Ví dụ kiểm thử khi data-testid là lựa chọn cuối cùng
it('should display data name using data-testid', async () => {
render(<AsyncDataDisplay />);
// Chờ đợi phần tử với data-testid='data-name' xuất hiện
const dataNameElement = await screen.findByTestId('data-name');
// Kiểm tra nội dung của phần tử đó
expect(dataNameElement).toHaveTextContent('Sample Data');
});
});
Giải thích:
- Chúng ta cần kiểm thử trạng thái ban đầu (loading) và trạng thái sau khi data được fetch.
expect(screen.getByText('Loading...')).toBeInTheDocument();
kiểm tra trạng thái loading ban đầu là đồng bộ.const dataNameElement = await screen.findByText('Sample Data');
sử dụngfindByText
, một async query. Thay vì chỉ tìm kiếm một lần và throw lỗi nếu không thấy nhưgetByText
,findByText
sẽ chờ đợi (với một timeout mặc định) cho đến khi phần tử thỏa mãn điều kiện xuất hiện trong DOM. Điều này rất hữu ích cho các nội dung được render sau khi fetch data.expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
sử dụngqueryByText
. Không giốnggetByText
vàfindByText
,queryByText
trả vềnull
nếu không tìm thấy phần tử, thay vì throw lỗi. Điều này phù hợp khi bạn muốn kiểm tra xem một phần tử không tồn tại trong DOM.
_Lưu ý:_ Trong các ứng dụng thực tế, bạn nên mock hàm fetchData
(như phần code bị comment ở trên) để kiểm soát dữ liệu trả về và tránh phụ thuộc vào network hoặc API thật trong quá trình kiểm thử unit component.
3. Kiểm thử Prop và Render có Điều kiện
Kiểm thử xem component render khác nhau dựa trên props hoặc trạng thái:
// src/components/StatusMessage.tsx
import React from 'react';
interface StatusMessageProps {
status: 'success' | 'error' | 'info';
message: string;
}
const StatusMessage: React.FC<StatusMessageProps> = ({ status, message }) => {
const statusClass = `status-${status}`; // CSS class ví dụ
return (
<div className={statusClass} role="status"> {/* role="status" cho accessibility */}
{message}
</div>
);
};
export default StatusMessage;
// src/components/__tests__/StatusMessage.test.tsx
import React from 'react';
import { render, screen } from '@testing-library/react';
import StatusMessage from '../StatusMessage';
describe('StatusMessage Component', () => {
it('should render success message with correct class', () => {
const message = 'Operation successful!';
render(<StatusMessage status="success" message={message} />);
// Tìm phần tử theo vai trò 'status'
const statusElement = screen.getByRole('status');
// Kiểm tra nội dung văn bản
expect(statusElement).toHaveTextContent(message);
// Kiểm tra class CSS (chi tiết triển khai, nên hạn chế nếu không thực sự cần)
// expect(statusElement).toHaveClass('status-success');
});
it('should render error message', () => {
const message = 'An error occurred.';
render(<StatusMessage status="error" message={message} />);
const statusElement = screen.getByRole('status');
expect(statusElement).toHaveTextContent(message);
});
it('should render info message', () => {
const message = 'Please review your information.';
render(<StatusMessage status="info" message={message} />);
const statusElement = screen.getByRole('status');
expect(statusElement).toHaveTextContent(message);
});
});
Giải thích:
- Chúng ta render component với các prop
status
khác nhau. - Sử dụng
screen.getByRole('status')
để tìm phần tử chính hiển thị thông báo. Vai tròstatus
là một vai trò accessibility tốt cho các thông báo cập nhật trạng thái không cần tương tác. expect(statusElement).toHaveTextContent(message)
kiểm tra xem phần tử có chứa đúng nội dung văn bản tương ứng với propmessage
hay không.
Các mẹo và thực hành tốt với Jest & RTL trong TypeScript
- Ưu tiên queries theo thứ tự: Khi tìm kiếm phần tử, hãy cố gắng tuân theo thứ tự ưu tiên được khuyến nghị bởi RTL:
getByRole
,getByLabelText
,getByPlaceholderText
,getByText
,getByDisplayValue
,getByAltText
,getByTitle
,getByTestId
. Bắt đầu vớigetByRole
là tốt nhất vì nó gần với cách người dùng tương tác và tốt cho accessibility. Chỉ sử dụnggetByTestId
như là phương án cuối cùng. - Test hành vi người dùng: Luôn tự hỏi "Người dùng sẽ làm gì để tương tác với phần này?" và viết bài kiểm thử mô phỏng hành vi đó.
- Tránh kiểm thử chi tiết triển khai: Đừng kiểm tra trạng thái nội bộ (
component.state
) hoặc gọi các phương thức của component instance (component.instance.someMethod()
). Điều này làm bài kiểm thử dễ vỡ. - Sử dụng
user-event
thay vìfireEvent
:user-event
cung cấp mô phỏng hành động người dùng chân thực hơn. - Sử dụng async utilities (
findBy*
,waitFor
) cho các tác vụ bất đồng bộ: Đừng cố gắng "đoán" thời điểm async code hoàn thành. - Kiểm thử các kịch bản ranh giới (edge cases): Kiểm tra các trường hợp đặc biệt như input rỗng, dữ liệu lỗi từ API, component không nhận props cần thiết, v.v.
- Sử dụng TypeScript để tăng cường độ tin cậy: TypeScript giúp bạn viết component và test props đúng kiểu, bắt lỗi sớm hơn.
- Giữ bài kiểm thử focused: Mỗi bài kiểm thử (
it
) nên tập trung vào một khía cạnh nhỏ, cụ thể của component. - Đảm bảo tính cô lập: Các bài kiểm thử cho một component không nên ảnh hưởng lẫn nhau. Sử dụng Jest mocking để cô lập component khỏi các dependencies phức tạp (như API calls thật).
Comments