Khi test component có gọi API, bạn không muốn gọi API thật (vì chậm, tốn tiền, data thay đổi). Bạn cần Mock.
Cách cũ: jest.spyOn(global, 'fetch'). (Dễ vỡ, phải mock từng response).
Cách mới: MSW (Mock Service Worker).
1. MSW là gì?
Nó tạo ra một Server giả (Interceptor) chặn mọi request HTTP outgoing từ Tests của bạn. Component của bạn cứ gọi axios/fetch như bình thường, MSW sẽ bắt lấy và trả về JSON bạn định nghĩa.
2. Setup MSW
Cài đặt: npm install -D msw
Tạo handlers (src/mocks/handlers.ts):
ts:
import { http, HttpResponse } from 'msw';
export const handlers = [
// Chặn GET /api/user
http.get('/api/user', () => {
return HttpResponse.json({
id: '123',
name: 'John Maverick',
})
}),
// Chặn POST /api/login
http.post('/api/login', () => {
return HttpResponse.json({ success: true })
}),
];Cấu hình Vitest Server (src/mocks/server.ts):
ts:
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);Cấu hình vitest.setup.ts:
ts:
import { server } from './mocks/server';
beforeAll(() => server.listen()); // Bắt đầu chặn
afterEach(() => server.resetHandlers()); // Reset sau mỗi test
afterAll(() => server.close()); // Dọn dẹp3. Viết Test Integration
tsx:
// UserProfile.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import UserProfile from './UserProfile';
// Không cần import mock api ở đây vì MSW lo rồi!
it('displays user name after fetching', async () => {
render(<UserProfile />);
// Ban đầu hiện loading
expect(screen.getByText(/loading/i)).toBeInTheDocument();
// Sau khi MSW trả data -> Hiện tên John
await waitFor(() => {
expect(screen.getByText('John Maverick')).toBeInTheDocument();
});
});4. Override Handler (Test Case Lỗi)
Đôi khi bạn muốn test trường hợp API lỗi 500.
tsx:
import { server } from '../mocks/server';
import { http, HttpResponse } from 'msw';
it('handles server error', async () => {
// Ghi đè handler chỉ cho test case này
server.use(
http.get('/api/user', () => {
return new HttpResponse(null, { status: 500 });
})
);
render(<UserProfile />);
await waitFor(() => {
expect(screen.getByText(/error fetching user/i)).toBeInTheDocument();
});
});