Skip to main content

Testing

DEVELOPER Intermediate

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(),
}));

Need help? Ask in GitHub Discussions.