Lesson 10: Quality Assurance and Testing the Frontend with React Testing Library and Jest
Goal
Set up automated tests to ensure the frontend components and React Query logic work as expected. We will use React Testing Library and Jest to test components, user interactions, and API integrations.
1. Learning Objectives
By the end of this lesson, students will:
- Understand the importance of testing React components.
- Learn to configure React Testing Library and Jest for a React project.
-
Write tests for:
- UI components (e.g., TodoItem, AddTodo).
- User interactions (e.g., clicking buttons, typing inputs).
- React Query data fetching and mutations.
2. Installing React Testing Library and Jest
Run the following command to install testing dependencies:
npm install --save-dev @testing-library/react @testing-library/jest-dom @testing-library/user-event jest3. Setting Up Jest for React Testing Library
- Ensure Jest Configuration: Update
package.jsonto include the following:
"scripts": {
"test": "react-scripts test",
"test:coverage": "react-scripts test --coverage"
}- Set Up Testing Environment:
Add the following line tosrc/setupTests.ts:
import '@testing-library/jest-dom';4. Writing Component Tests
We will test:
- Rendering components correctly.
- User interactions such as typing, clicking buttons, and updating state.
4.1. Test the AddTodo Component
File: src/components/__tests__/AddTodo.test.tsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import AddTodo from '../AddTodo';
const queryClient = new QueryClient();
describe('AddTodo Component', () => {
test('renders input field and add button', () => {
render(
<QueryClientProvider client={queryClient}>
<AddTodo />
</QueryClientProvider>
);
expect(screen.getByPlaceholderText(/add a new task/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /add task/i })).toBeInTheDocument();
});
test('calls mutation when the form is submitted', () => {
render(
<QueryClientProvider client={queryClient}>
<AddTodo />
</QueryClientProvider>
);
const input = screen.getByPlaceholderText(/add a new task/i);
const button = screen.getByRole('button', { name: /add task/i });
fireEvent.change(input, { target: { value: 'New Todo' } });
fireEvent.click(button);
expect(button).toBeDisabled(); // Simulate loading state
});
});Explanation:
- The first test checks if the input field and button render correctly.
- The second test simulates a user typing into the input field and clicking the "Add Task" button.
4.2. Test the TodoItem Component
File: src/components/__tests__/TodoItem.test.tsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import TodoItem from '../TodoItem';
describe('TodoItem Component', () => {
const mockDelete = jest.fn();
const mockToggle = jest.fn();
test('renders task title and delete button', () => {
render(
<TodoItem
id="1"
title="Sample Task"
completed={false}
onDelete={mockDelete}
onToggle={mockToggle}
/>
);
expect(screen.getByText(/sample task/i)).toBeInTheDocument();
expect(screen.getByRole('button')).toBeInTheDocument();
});
test('calls onDelete when delete button is clicked', () => {
render(
<TodoItem
id="1"
title="Sample Task"
completed={false}
onDelete={mockDelete}
onToggle={mockToggle}
/>
);
fireEvent.click(screen.getByRole('button'));
expect(mockDelete).toHaveBeenCalledTimes(1);
});
test('calls onToggle when checkbox is clicked', () => {
render(
<TodoItem
id="1"
title="Sample Task"
completed={false}
onDelete={mockDelete}
onToggle={mockToggle}
/>
);
fireEvent.click(screen.getByRole('checkbox'));
expect(mockToggle).toHaveBeenCalledTimes(1);
});
});5. Testing React Query API Integration
We’ll mock the API calls in TodoList using Jest.
File: src/components/__tests__/TodoList.test.tsx
import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import TodoList from '../TodoList';
import { fetchTodos } from '../../api/todoApi';
jest.mock('../../api/todoApi', () => ({
fetchTodos: jest.fn(),
}));
const queryClient = new QueryClient();
describe('TodoList Component', () => {
test('renders loading state', () => {
render(
<QueryClientProvider client={queryClient}>
<TodoList />
</QueryClientProvider>
);
expect(screen.getByText(/loading/i)).toBeInTheDocument();
});
test('renders fetched todos', async () => {
const mockTodos = [
{ _id: '1', title: 'Learn React', completed: false },
{ _id: '2', title: 'Learn Jest', completed: true },
];
(fetchTodos as jest.Mock).mockResolvedValue(mockTodos);
render(
<QueryClientProvider client={queryClient}>
<TodoList />
</QueryClientProvider>
);
await waitFor(() => {
expect(screen.getByText(/learn react/i)).toBeInTheDocument();
expect(screen.getByText(/learn jest/i)).toBeInTheDocument();
});
});
});6. Running the Tests
- Run all tests:
npm test- Generate a test coverage report:
npm run test:coverage7. Homework Assignment
-
Write a test for the AI Tools Page to check:
- Input renders correctly.
- Clicking "Generate Description" triggers an API call.
- Ensure all components have 100% coverage by running
npm run test:coverage. - Add tests for edge cases, such as API errors or missing props.
Next Lesson Preview: Lesson 11
In Lesson 11, we will:
- Review everything we’ve built so far.
- Discuss deployment of the full-stack application using Vercel (frontend) and Render (backend).
