Skip to main content

Setting up Jest and React Testing Library (Part 2) #147

📖 Introduction

In Setting up Jest and React Testing Library (Part 1), we introduced Jest and React Testing Library (RTL) and covered their installation. This second part focuses on finalizing the practical setup: configuring Jest, adding a test script to package.json, and writing and running our very first simple React component test. By the end of this article, you'll have a fully operational testing environment ready for action.


📚 Prerequisites

Before we begin, ensure you have:

  • Completed Part 1: Installed Jest, RTL, and related dependencies (@testing-library/jest-dom, @testing-library/user-event, babel-jest, etc., if not using CRA).
  • A basic React project.
  • Familiarity with your project's package.json file.

🎯 Article Outline: What You'll Master

In this article, you will learn:

  • Finalizing Jest Configuration: Creating or modifying jest.config.js.
  • Setting up src/setupTests.js: Importing @testing-library/jest-dom for custom matchers.
  • Mocking Static Assets: Handling CSS and file imports in Jest.
  • Adding Test Scripts to package.json: test and test:watch scripts.
  • Writing a Simple Test: Creating your first *.test.js file for a basic component.
  • Running Tests: Executing tests and understanding the output.
  • Troubleshooting Common Setup Issues (Briefly).

🧠 Section 1: Finalizing Jest Configuration (jest.config.js)

In Part 1, we conceptually outlined a jest.config.js. Let's ensure it's created at the root of your project and properly configured. If you used Create React App, much of this might be handled internally or in package.json, but having a separate jest.config.js offers more explicit control, especially if you eject or have a custom setup.

Create/Edit jest.config.js (at your project root):

// jest.config.js
module.exports = {
// Specifies the test environment for Jest. 'jsdom' simulates a browser environment.
testEnvironment: 'jest-environment-jsdom', // or simply 'jsdom'

// A list of paths to modules that run some code to configure or set up the testing framework
// before each test file in the suite is executed.
setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],

// A map from regular expressions to paths to transformers. A transformer is a module
// that provides a synchronous function for transforming source files.
transform: {
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest', // Use babel-jest for JS/JSX/TS/TSX files
// If you are not using Babel (e.g., Vite with esbuild/SWC for tests),
// you might use a different transformer like 'ts-jest' for TypeScript directly
// or rely on Vite's specific Jest integration if available.
},

// An array of file extensions your modules use.
moduleFileExtensions: ['js', 'jsx', 'json', 'node', 'ts', 'tsx'],

// A map from regular expressions to module names or to arrays of module names
// that allow to stub out resources with a single module.
moduleNameMapper: {
// Mock CSS Modules or global CSS imports:
// If you import CSS files in your components, Jest needs to know how to handle them.
// 'identity-obj-proxy' makes CSS Modules export their class names as strings.
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',

// Mock static file assets (images, fonts, etc.)
// Replace them with a simple string stub during tests.
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/__mocks__/fileMock.js',
},

// Indicates whether each individual test should be reported during the run.
verbose: true,

// Automatically clear mock calls, instances, contexts and results before every test.
// Good for test isolation.
clearMocks: true,

// The directory where Jest should output its coverage files.
// coverageDirectory: 'coverage',

// An array of glob patterns indicating a set of files for which coverage information should be collected.
// collectCoverageFrom: [
// 'src/**/*.{js,jsx,ts,tsx}',
// '!src/**/*.d.ts', // Exclude TypeScript definition files
// '!src/index.js', // Exclude entry point if not much logic
// '!src/setupTests.js', // Exclude test setup
// '!src/reportWebVitals.js', // Exclude web vitals if present
// ],
};

Key additions/clarifications from Part 1:

  • verbose: true: Provides more detailed output when tests run.
  • clearMocks: true: Resets mocks between tests, which is good for test isolation.
  • moduleNameMapper for CSS and static files: This is crucial. When Jest encounters import './MyComponent.css'; or import logo from './logo.svg';, it doesn't know how to process these non-JavaScript files.
    • identity-obj-proxy: A useful npm package (npm install --save-dev identity-obj-proxy) for mocking CSS Modules. It makes styles.myClass return the string "myClass". For global CSS, it effectively makes the import a no-op or an empty object.
    • <rootDir>/__mocks__/fileMock.js: Points to a mock file for other static assets.

💻 Section 2: Setting up src/setupTests.js

This file, referenced in jest.config.js's setupFilesAfterEnv array, is executed before each test file. Its primary purpose is to extend Jest's expect with custom DOM matchers from @testing-library/jest-dom.

Create src/setupTests.js:

// 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';

// You can add any other global setup here, for example:
// - Mocking global objects (e.g., window.matchMedia, window.localStorage)
// - Setting up a global server for MSW (Mock Service Worker) if you use it for API mocking

// Example: Mocking window.matchMedia (common requirement for some UI libraries)
// Object.defineProperty(window, 'matchMedia', {
// writable: true,
// value: jest.fn().mockImplementation(query => ({
// matches: false,
// media: query,
// onchange: null,
// addListener: jest.fn(), // deprecated
// removeListener: jest.fn(), // deprecated
// addEventListener: jest.fn(),
// removeEventListener: jest.fn(),
// dispatchEvent: jest.fn(),
// })),
// });

The most important line is import '@testing-library/jest-dom';. This makes matchers like toBeInTheDocument(), toHaveTextContent(), toBeVisible(), etc., available in all your test files.


🛠️ Section 3: Mocking Static Assets

As configured in jest.config.js, we need to create the mock file for static assets.

Create __mocks__/fileMock.js (at your project root, in a directory named __mocks__):

// __mocks__/fileMock.js
// This is a simple mock for file imports (images, fonts, etc.)
// When Jest encounters an import for a file type configured in moduleNameMapper
// to use this mock, it will replace the import with this module's export.
module.exports = 'test-file-stub';

This means that if your component imports an image like import logo from './logo.png';, in your tests, the logo variable will simply be the string 'test-file-stub'. This prevents Jest from erroring on non-JavaScript files and allows your components to render without breaking.


🔬 Section 4: Adding Test Scripts to package.json

Now, let's add scripts to package.json to easily run Jest.

Edit package.json:

{
"name": "my-react-app",
"version": "0.1.0",
// ... other configurations ...
"scripts": {
"start": "react-scripts start", // Or your dev script (e.g., "vite")
"build": "react-scripts build", // Or your build script (e.g., "vite build")
"test": "jest", // Runs Jest once
"test:watch": "jest --watch", // Runs Jest in watch mode
"test:coverage": "jest --coverage" // Runs Jest and generates a coverage report
// "eject": "react-scripts eject" // If using CRA
},
// ... dependencies and devDependencies ...
}

Explanation of Scripts:

  • "test": "jest": This command will run all tests found by Jest (typically files named *.test.js, *.spec.js, or files in a __tests__ directory) once and then exit.
  • "test:watch": "jest --watch": This is very useful during development. Jest will start in "watch mode," initially running all tests. Then, it will watch for file changes and automatically re-run only the tests relevant to the changed files. It also provides an interactive CLI to filter tests, update snapshots, etc.
  • "test:coverage": "jest --coverage": This command runs all tests and then generates a code coverage report (usually in a coverage/ directory at the project root), showing which parts of your code are covered by tests.

If you're using Create React App, the test script is usually already configured as "react-scripts test", which internally uses Jest. You can still add "test:watch": "react-scripts test --watchAll=false" (or similar, react-scripts test runs in watch mode by default) and customize as needed. react-scripts test --coverage --watchAll=false generates coverage.


✨ Section 5: Writing a Simple Test

Let's create a very basic React component and write our first test for it.

1. Create a Simple Component:

// src/components/Greeting.jsx
import React from 'react';

const Greeting = ({ name }) => {
if (!name) {
return <div>Hello, Guest!</div>;
}
return <div>Hello, {name}!</div>;
};

export default Greeting;

2. Create a Test File: Jest automatically discovers test files. Common conventions are:

  • Placing test files in a __tests__ directory alongside the component (e.g., src/components/__tests__/Greeting.test.js).
  • Naming test files with .test.js or .spec.js suffixes (e.g., src/components/Greeting.test.js).

Let's use the latter: Create src/components/Greeting.test.js:

// src/components/Greeting.test.js
import React from 'react';
import { render, screen } from '@testing-library/react'; // Import render and screen from RTL
import Greeting from './Greeting'; // Import the component to test

// Test suite - a group of related tests
describe('Greeting Component', () => {
// Individual test case
test('renders "Hello, Guest!" when no name prop is provided', () => {
// 1. Render the component
render(<Greeting />);

// 2. Find elements in the rendered output
// screen.getByText() will throw an error if the text is not found
const guestGreeting = screen.getByText('Hello, Guest!');
// For debugging, you can see the rendered DOM:
// screen.debug();

// 3. Assert that the element is in the document
expect(guestGreeting).toBeInTheDocument();
});

test('renders "Hello, [name]!" when a name prop is provided', () => {
const testName = 'Alice';
render(<Greeting name={testName} />);

// Find element by text, using a regular expression for flexibility
const nameGreeting = screen.getByText(`Hello, ${testName}!`); // Or screen.getByText(/Hello, Alice!/i)

expect(nameGreeting).toBeInTheDocument();
});

test('renders correctly with a different name', () => {
const testName = 'Bob';
render(<Greeting name={testName} />);

expect(screen.getByText(`Hello, ${testName}!`)).toBeInTheDocument();
// Check that the guest greeting is NOT present
expect(screen.queryByText('Hello, Guest!')).not.toBeInTheDocument();
});
});

Explanation of the Test File:

  • import React from 'react';: Needed because we're writing JSX.
  • import { render, screen } from '@testing-library/react';:
    • render: RTL's function to render a React component into a JSDOM container.
    • screen: An object that provides query methods (like getByText, getByRole) that are pre-bound to the document.body of the rendered component. This is the recommended way to query.
  • import Greeting from './Greeting';: Import the component we want to test.
  • describe('Greeting Component', () => { ... });: describe is a Jest global function that groups related tests into a "test suite."
  • test('renders "Hello, Guest!" ...', () => { ... });: test (or its alias it) is a Jest global function that defines an individual test case. The first argument is a descriptive string for the test.
  • render(<Greeting />);: Renders the Greeting component.
  • screen.getByText('Hello, Guest!');: Queries the rendered output for an element containing the exact text "Hello, Guest!". If not found, it throws an error (failing the test).
  • expect(guestGreeting).toBeInTheDocument();: This is an assertion using Jest's expect and a matcher from @testing-library/jest-dom. It asserts that the guestGreeting element was found in the document.
  • screen.queryByText(...): queryByText is used when you want to assert that an element is not present. It returns null if not found (instead of throwing an error like getByText).

✨ Section 6: Running Tests

Now, open your terminal in the project root and run one of the scripts:

  1. Run all tests once:

    npm test
    # or
    yarn test

    You should see output similar to this (if all tests pass):

    PASS  src/components/Greeting.test.js
    Greeting Component
    ✓ renders "Hello, Guest!" when no name prop is provided (5ms)
    ✓ renders "Hello, [name]!" when a name prop is provided (1ms)
    ✓ renders correctly with a different name (1ms)

    Test Suites: 1 passed, 1 total
    Tests: 3 passed, 3 total
    Snapshots: 0 total
    Time: 1.234s
    Ran all test suites.
  2. Run tests in watch mode:

    npm run test:watch
    # or
    yarn test:watch

    Jest will run the tests and then wait for file changes. Try modifying Greeting.jsx or Greeting.test.js and see Jest automatically re-run the tests. Press q to quit watch mode.


💡 Section 7: Troubleshooting Common Setup Issues (Briefly)

  • "SyntaxError: Cannot use import statement outside a module" or JSX errors: Usually means Jest isn't transforming your code correctly. Ensure babel-jest is installed and your babel.config.js (or equivalent) is set up for @babel/preset-env and @babel/preset-react. Check the transform option in jest.config.js.
  • "TypeError: expect(...).toBeInTheDocument is not a function": Means @testing-library/jest-dom isn't set up correctly. Ensure it's imported in your src/setupTests.js file and that setupFilesAfterEnv in jest.config.js points to it.
  • Errors related to CSS/image imports: Your moduleNameMapper in jest.config.js is likely missing or misconfigured. Ensure identity-obj-proxy is installed for CSS and you have a fileMock.js.
  • "ReferenceError: document is not defined": Ensure testEnvironment in jest.config.js is set to 'jsdom' or 'jest-environment-jsdom'.

Consult the Jest and RTL documentation for more detailed troubleshooting.


Conclusion & Key Takeaways (Part 2)

Congratulations! You've successfully configured Jest and React Testing Library, added test scripts, and written and run your first React component tests. This setup forms the backbone of your unit and integration testing strategy.

Key Takeaways:

  • jest.config.js is used to configure Jest's behavior, including test environment, transformers, and asset mocking.
  • src/setupTests.js is crucial for global test setup, like importing @testing-library/jest-dom.
  • Mocking CSS and static files is necessary for Jest to run tests on components that import them.
  • npm test and npm run test:watch are common scripts for running tests.
  • RTL's render and screen are used to render components and query their output in a user-centric way.

➡️ Next Steps

With your testing environment fully operational, we're ready to dive into writing more comprehensive tests. In the next article, "Writing Your First Unit Tests (Part 1)", we'll explore different types of assertions, querying elements in more detail, and testing basic component interactions.

Happy testing!


glossary

  • jest.config.js: The configuration file for Jest.
  • setupFilesAfterEnv: A Jest configuration option pointing to setup files that run before each test suite.
  • moduleNameMapper: A Jest configuration option for mocking module imports (e.g., CSS, images).
  • identity-obj-proxy: An npm package used to mock CSS Modules in Jest tests.
  • Test Suite (describe block): A group of related test cases.
  • Test Case (test or it block): An individual test that verifies a specific piece of functionality.
  • render (RTL): A function from React Testing Library to render a component into a JSDOM environment.
  • screen (RTL): An object from React Testing Library providing query methods bound to the rendered component's document.
  • Matcher (Jest): Functions used with expect to make assertions (e.g., toBe, toEqual, toBeInTheDocument).

Further Reading