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:

  1. Understand the importance of testing React components.
  2. Learn to configure React Testing Library and Jest for a React project.
  3. 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 jest

3. Setting Up Jest for React Testing Library

  1. Ensure Jest Configuration: Update package.json to include the following:
"scripts": {
  "test": "react-scripts test",
  "test:coverage": "react-scripts test --coverage"
}
  1. Set Up Testing Environment:
    Add the following line to src/setupTests.ts:
import '@testing-library/jest-dom';

4. Writing Component Tests

We will test:

  1. Rendering components correctly.
  2. 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:

  1. The first test checks if the input field and button render correctly.
  2. 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

  1. Run all tests:
npm test
  1. Generate a test coverage report:
npm run test:coverage

7. Homework Assignment

  1. Write a test for the AI Tools Page to check:

    • Input renders correctly.
    • Clicking "Generate Description" triggers an API call.
  2. Ensure all components have 100% coverage by running npm run test:coverage.
  3. 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).

Copyright © Big Poppa Code & Progress and Fortune LLC 2025