Testing
Test React components and applications.
Overview
This guide covers testing strategies for EZ-Console React applications, including unit tests, integration tests, and best practices.
Testing Setup
Install Dependencies
pnpm add -D @testing-library/react @testing-library/jest-dom @testing-library/user-event vitest jsdom
Configure Vitest
Create vitest.config.ts:
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
},
});
Setup File
Create src/test/setup.ts:
import '@testing-library/jest-dom';
import { expect, afterEach } from 'vitest';
import { cleanup } from '@testing-library/react';
afterEach(() => {
cleanup();
});
Component Testing
Basic Component Test
import { render, screen } from '@testing-library/react';
import { ProductCard } from './ProductCard';
describe('ProductCard', () => {
it('renders product name', () => {
const product = {
id: '1',
name: 'Test Product',
price: 99.99,
};
render(<ProductCard product={product} />);
expect(screen.getByText('Test Product')).toBeInTheDocument();
});
});
Testing with User Interaction
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ProductForm } from './ProductForm';
describe('ProductForm', () => {
it('submits form with valid data', async () => {
const user = userEvent.setup();
const onSubmit = vi.fn();
render(<ProductForm onSubmit={onSubmit} />);
await user.type(screen.getByLabelText('Name'), 'Test Product');
await user.type(screen.getByLabelText('Price'), '99.99');
await user.click(screen.getByRole('button', { name: 'Submit' }));
expect(onSubmit).toHaveBeenCalledWith({
name: 'Test Product',
price: 99.99,
});
});
});
Testing with Context
Mock Auth Context
import { render } from '@testing-library/react';
import { AuthProvider } from '@/contexts/AuthContext';
const renderWithAuth = (component: React.ReactElement) => {
return render(
<AuthProvider>
{component}
</AuthProvider>
);
};
describe('ProductList', () => {
it('renders for authenticated user', () => {
renderWithAuth(<ProductList />);
// Test implementation
});
});
Testing API Calls
Mock API Calls
import { render, screen, waitFor } from '@testing-library/react';
import { apiGet } from 'ez-console';
import { ProductList } from './ProductList';
vi.mock('ez-console', () => ({
apiGet: vi.fn(),
}));
describe('ProductList', () => {
it('fetches and displays products', async () => {
const mockProducts = [
{ id: '1', name: 'Product 1', price: 99.99 },
{ id: '2', name: 'Product 2', price: 149.99 },
];
(apiGet as any).mockResolvedValue({
data: mockProducts,
total: 2,
});
render(<ProductList />);
await waitFor(() => {
expect(screen.getByText('Product 1')).toBeInTheDocument();
expect(screen.getByText('Product 2')).toBeInTheDocument();
});
});
});
Testing Forms
Form Validation Test
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ProductForm } from './ProductForm';
describe('ProductForm', () => {
it('shows validation error for empty name', async () => {
const user = userEvent.setup();
render(<ProductForm />);
await user.click(screen.getByRole('button', { name: 'Submit' }));
expect(await screen.findByText('Name is required')).toBeInTheDocument();
});
});
Testing Hooks
Custom Hook Test
import { renderHook, act } from '@testing-library/react';
import { useProductList } from './useProductList';
describe('useProductList', () => {
it('fetches products on mount', async () => {
const { result } = renderHook(() => useProductList());
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0));
});
expect(result.current.products).toHaveLength(2);
});
});
Snapshot Testing
import { render } from '@testing-library/react';
import { ProductCard } from './ProductCard';
describe('ProductCard', () => {
it('matches snapshot', () => {
const product = {
id: '1',
name: 'Test Product',
price: 99.99,
};
const { container } = render(<ProductCard product={product} />);
expect(container).toMatchSnapshot();
});
});
Best Practices
1. Test User Behavior
// ✅ Good: Test what user sees
expect(screen.getByText('Product Name')).toBeInTheDocument();
// ❌ Bad: Test implementation details
expect(component.state.name).toBe('Product Name');
2. Use Accessible Queries
// ✅ Good: Accessible queries
screen.getByRole('button', { name: 'Submit' });
screen.getByLabelText('Name');
// ❌ Bad: Non-accessible queries
screen.getByTestId('submit-button');
3. Clean Up
// ✅ Good: Automatic cleanup
afterEach(() => {
cleanup();
});
4. Mock External Dependencies
// ✅ Good: Mock API calls
vi.mock('ez-console', () => ({
apiGet: vi.fn(),
}));
Related Topics
- Forms & Validation - Test form validation
- State Management - Test state updates
Need help? Ask in GitHub Discussions.