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
andtest: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 encountersimport './MyComponent.css';
orimport 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 makesstyles.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 acoverage/
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 (likegetByText
,getByRole
) that are pre-bound to thedocument.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 aliasit
) is a Jest global function that defines an individual test case. The first argument is a descriptive string for the test.render(<Greeting />);
: Renders theGreeting
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'sexpect
and a matcher from@testing-library/jest-dom
. It asserts that theguestGreeting
element was found in the document.screen.queryByText(...)
:queryByText
is used when you want to assert that an element is not present. It returnsnull
if not found (instead of throwing an error likegetByText
).
✨ Section 6: Running Tests
Now, open your terminal in the project root and run one of the scripts:
-
Run all tests once:
npm test
# or
yarn testYou 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. -
Run tests in watch mode:
npm run test:watch
# or
yarn test:watchJest will run the tests and then wait for file changes. Try modifying
Greeting.jsx
orGreeting.test.js
and see Jest automatically re-run the tests. Pressq
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 yourbabel.config.js
(or equivalent) is set up for@babel/preset-env
and@babel/preset-react
. Check thetransform
option injest.config.js
. - "TypeError: expect(...).toBeInTheDocument is not a function": Means
@testing-library/jest-dom
isn't set up correctly. Ensure it's imported in yoursrc/setupTests.js
file and thatsetupFilesAfterEnv
injest.config.js
points to it. - Errors related to CSS/image imports: Your
moduleNameMapper
injest.config.js
is likely missing or misconfigured. Ensureidentity-obj-proxy
is installed for CSS and you have afileMock.js
. - "ReferenceError: document is not defined": Ensure
testEnvironment
injest.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
andnpm run test:watch
are common scripts for running tests.- RTL's
render
andscreen
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
orit
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
- Jest: Getting Started
- Jest: Configuration
- React Testing Library: Setup
@testing-library/jest-dom
GitHub (Matcher list)- Common mistakes with React Testing Library (by Kent C. Dodds)