Master testing techniques for React applications
Testing individual components with Jest
import { render, screen } from '@testing-library/react';
import Counter from './Counter';
describe('Counter Component', () => {
test('renders initial count of zero', () => {
render( );
const countElement = screen.getByText(/count: 0/i);
expect(countElement).toBeInTheDocument();
});
test('increments count when button is clicked', () => {
render( );
const button = screen.getByRole('button', { name: /increment/i });
fireEvent.click(button);
expect(screen.getByText(/count: 1/i)).toBeInTheDocument();
});
});
import { renderHook, act } from '@testing-library/react-hooks';
import useCounter from './useCounter';
describe('useCounter Hook', () => {
test('should increment counter', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
test('should decrement counter', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.decrement();
});
expect(result.current.count).toBe(-1);
});
});
Testing component interactions
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import LoginForm from './LoginForm';
describe('LoginForm', () => {
test('submits form with user credentials', async () => {
const onSubmit = jest.fn();
render( );
fireEvent.change(screen.getByLabelText(/email/i), {
target: { value: 'test@example.com' }
});
fireEvent.change(screen.getByLabelText(/password/i), {
target: { value: 'password123' }
});
fireEvent.click(screen.getByRole('button', { name: /submit/i }));
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'password123'
});
});
});
test('displays validation errors', async () => {
render( );
fireEvent.click(screen.getByRole('button', { name: /submit/i }));
expect(await screen.findByText(/email is required/i)).toBeInTheDocument();
expect(await screen.findByText(/password is required/i)).toBeInTheDocument();
});
});
End-to-end testing with Cypress
describe('Authentication Flow', () => {
beforeEach(() => {
cy.visit('/');
});
it('should login successfully', () => {
cy.get('[data-cy=login-email]')
.type('user@example.com');
cy.get('[data-cy=login-password]')
.type('password123');
cy.get('[data-cy=login-submit]')
.click();
cy.url()
.should('include', '/dashboard');
cy.get('[data-cy=welcome-message]')
.should('contain', 'Welcome back');
});
it('should show error for invalid credentials', () => {
cy.get('[data-cy=login-email]')
.type('invalid@example.com');
cy.get('[data-cy=login-password]')
.type('wrongpassword');
cy.get('[data-cy=login-submit]')
.click();
cy.get('[data-cy=error-message]')
.should('be.visible')
.and('contain', 'Invalid credentials');
});
});
describe('Dashboard', () => {
beforeEach(() => {
cy.intercept('GET', '/api/user/profile', {
fixture: 'profile.json'
}).as('getProfile');
cy.intercept('GET', '/api/dashboard/stats', {
fixture: 'stats.json'
}).as('getStats');
cy.visit('/dashboard');
});
it('should display user data', () => {
cy.wait('@getProfile');
cy.get('[data-cy=user-name]')
.should('contain', 'John Doe');
cy.wait('@getStats');
cy.get('[data-cy=total-orders]')
.should('contain', '150');
});
it('should handle API errors', () => {
cy.intercept('GET', '/api/dashboard/stats', {
statusCode: 500,
body: { error: 'Server error' }
}).as('getStatsError');
cy.visit('/dashboard');
cy.get('[data-cy=error-message]')
.should('be.visible');
});
});