← Về danh sách bài họcBài 23/25

🧪 Bài 23: Testing - Kiểm Thử Components

⏱️ Thời gian đọc: 18 phút | 📚 Độ khó: Nâng cao

🎯 Sau bài học này, bạn sẽ:

1. Setup Testing

npm install -D vitest @testing-library/react @testing-library/jest-dom @testing-library/user-event jsdom
// vite.config.js
export default defineConfig({
    test: {
        environment: 'jsdom',
        setupFiles: './src/test/setup.js',
        globals: true,
    }
});

// src/test/setup.js
import '@testing-library/jest-dom';

2. Component Testing

// Counter.jsx
function Counter() {
    const [count, setCount] = useState(0);
    return (
        <div>
            <p data-testid="count">Count: {count}</p>
            <button onClick={() => setCount(c => c + 1)}>Increment</button>
            <button onClick={() => setCount(0)}>Reset</button>
        </div>
    );
}

// Counter.test.jsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

describe('Counter', () => {
    it('hiển thị giá trị ban đầu', () => {
        render(<Counter />);
        expect(screen.getByTestId('count')).toHaveTextContent('Count: 0');
    });

    it('tăng count khi click', async () => {
        const user = userEvent.setup();
        render(<Counter />);

        await user.click(screen.getByText('Increment'));
        expect(screen.getByTestId('count')).toHaveTextContent('Count: 1');

        await user.click(screen.getByText('Increment'));
        expect(screen.getByTestId('count')).toHaveTextContent('Count: 2');
    });

    it('reset về 0', async () => {
        const user = userEvent.setup();
        render(<Counter />);

        await user.click(screen.getByText('Increment'));
        await user.click(screen.getByText('Reset'));
        expect(screen.getByTestId('count')).toHaveTextContent('Count: 0');
    });
});

3. Testing Async & API Calls

import { render, screen, waitFor } from '@testing-library/react';

// Mock fetch
globalThis.fetch = vi.fn();

describe('UserList', () => {
    it('hiển thị users từ API', async () => {
        fetch.mockResolvedValueOnce({
            ok: true,
            json: () => Promise.resolve([
                { id: 1, name: 'An' },
                { id: 2, name: 'Bình' }
            ])
        });

        render(<UserList />);

        // Chờ loading kết thúc
        expect(screen.getByText('Loading...')).toBeInTheDocument();
        await waitFor(() => {
            expect(screen.getByText('An')).toBeInTheDocument();
            expect(screen.getByText('Bình')).toBeInTheDocument();
        });
    });

    it('hiển thị lỗi khi API fail', async () => {
        fetch.mockRejectedValueOnce(new Error('Network error'));
        render(<UserList />);
        await waitFor(() => {
            expect(screen.getByText(/error/i)).toBeInTheDocument();
        });
    });
});

📝 Tóm Tắt