Skip to main content
This project uses React Testing Library and Jest for frontend testing. The testing setup is provided by Create React App (react-scripts).

Testing Stack

React Testing Library

Version: 16.3.0User-centric testing approach for React components

Jest

Included in: react-scripts 5.0.1Test runner and assertion library

jest-dom

Version: 6.9.1Custom Jest matchers for DOM assertions

User Event

Version: 13.5.0Simulate user interactions

Running Tests

All test commands should be run from the client directory, as tests are only configured for the React frontend.

Interactive Watch Mode

The default way to run tests is in watch mode, which automatically re-runs tests when files change:
cd client
npm test
This will start Jest in watch mode:
 PASS  src/App.test.js
  ✓ renders learn react link (24 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.234 s
Ran all test suites related to changed files.

Watch Usage
 › Press a to run all tests.
 › Press f to run only failed tests.
 › Press q to quit watch mode.
 › Press p to filter by a filename regex pattern.
 › Press t to filter by a test name regex pattern.
 › Press Enter to trigger a test run.

Run All Tests Once

To run all tests once without watch mode (useful for CI/CD):
cd client
CI=true npm test
Or to run with coverage:
cd client
npm test -- --coverage --watchAll=false

Test Configuration

The testing environment is configured automatically by Create React App. Key configuration:

Test Setup File

The client/src/setupTests.js file runs before each test:
client/src/setupTests.js
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
This imports jest-dom matchers, giving you access to useful assertions like:
  • toBeInTheDocument()
  • toHaveTextContent()
  • toBeVisible()
  • toBeDisabled()

ESLint Configuration

The project includes ESLint configuration for testing in client/package.json:21-25:
"eslintConfig": {
  "extends": [
    "react-app",
    "react-app/jest"
  ]
}

Example Test

Here’s the example test included in the project (client/src/App.test.js):
client/src/App.test.js
import { render, screen } from '@testing-library/react';
import App from './App';

test('renders learn react link', () => {
  render(<App />);
  const linkElement = screen.getByText(/learn react/i);
  expect(linkElement).toBeInTheDocument();
});
This test:
  1. Renders the App component
  2. Finds an element with text matching “learn react” (case-insensitive)
  3. Asserts that the element is in the document

Writing Tests

1

Create a test file

Create a file next to your component with the .test.js extension:
src/
  ├── MyComponent.js
  └── MyComponent.test.js
Or use the .spec.js extension - both work with Create React App.
2

Import testing utilities

import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import MyComponent from './MyComponent';
3

Write your test

test('displays welcome message', () => {
  render(<MyComponent />);
  expect(screen.getByText('Welcome')).toBeInTheDocument();
});

test('handles button click', async () => {
  const user = userEvent.setup();
  render(<MyComponent />);
  
  const button = screen.getByRole('button', { name: /click me/i });
  await user.click(button);
  
  expect(screen.getByText('Clicked!')).toBeInTheDocument();
});

Testing Best Practices

React Testing Library encourages testing components the way users interact with them, rather than testing implementation details.
Prefer queries that reflect how users find elements:
// Good - accessible queries
screen.getByRole('button', { name: /submit/i })
screen.getByLabelText(/email address/i)
screen.getByText(/welcome/i)

// Avoid - implementation details
screen.getByTestId('submit-button')
screen.getByClassName('btn-primary')
userEvent more closely simulates actual user interactions:
import userEvent from '@testing-library/user-event';

test('form submission', async () => {
  const user = userEvent.setup();
  render(<LoginForm />);

  await user.type(screen.getByLabelText(/email/i), '[email protected]');
  await user.click(screen.getByRole('button', { name: /login/i }));

  expect(screen.getByText(/welcome/i)).toBeInTheDocument();
});
Focus on what the user sees and experiences:
// Good - tests user-visible behavior
test('shows error message on failed login', async () => {
  render(<LoginForm />);
  // ... trigger failed login ...
  expect(screen.getByText(/invalid credentials/i)).toBeInTheDocument();
});

// Avoid - tests internal state
test('sets error state to true', () => {
  const { result } = renderHook(() => useAuth());
  // Testing internal state
});
Wait for elements to appear using async queries:
import { render, screen, waitFor } from '@testing-library/react';

test('loads and displays data', async () => {
  render(<DataComponent />);

  // Wait for element to appear
  const data = await screen.findByText(/loaded data/i);
  expect(data).toBeInTheDocument();

  // Or use waitFor for complex assertions
  await waitFor(() => {
    expect(screen.getByText(/status: complete/i)).toBeInTheDocument();
  });
});

Watch Mode Commands

When running npm test, you have access to several interactive commands:

Press a

Run all tests

Press f

Run only failed tests

Press p

Filter by filename pattern

Press t

Filter by test name pattern

Press q

Quit watch mode

Press Enter

Trigger a test run

Backend Testing

The server currently has no testing setup. Consider adding tests for the Express API.
To add backend testing, you could:
  1. Install testing dependencies:
    cd server
    npm install --save-dev jest supertest
    
  2. Add a test script to server/package.json:
    {
      "scripts": {
        "test": "jest"
      }
    }
    
  3. Create API tests:
    const request = require('supertest');
    const app = require('./index');
    
    describe('GET /', () => {
      it('responds with hello message', async () => {
        const response = await request(app).get('/');
        expect(response.status).toBe(200);
        expect(response.text).toContain('Mini project Deployment');
      });
    });
    

Coverage Reports

Generate test coverage reports:
cd client
npm test -- --coverage --watchAll=false
This creates a coverage report:
---------------------|---------|----------|---------|---------|-------------------
File                 | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
---------------------|---------|----------|---------|---------|-------------------
All files            |   100   |   100    |   100   |   100   |                   
 App.js              |   100   |   100    |   100   |   100   |                   
 index.js            |   100   |   100    |   100   |   100   |                   
 reportWebVitals.js  |   100   |   100    |   100   |   100   |                   
---------------------|---------|----------|---------|---------|-------------------
HTML coverage reports are generated in client/coverage/lcov-report/index.html.

Continuous Integration

For CI/CD pipelines (like Jenkins), use:
cd client
CI=true npm test -- --coverage
This:
  • Runs tests once (no watch mode)
  • Generates coverage reports
  • Exits with appropriate status codes
  • Works well in Jenkins, GitHub Actions, etc.

Next Steps

Setup Guide

Review development environment setup

Running Locally

Learn how to run the application

CI/CD with Jenkins

Set up automated testing in Jenkins

Build docs developers (and LLMs) love