João Vinezof
testing

Dec 27, 2024

React Unit Testing: A Complete Guide with Jest & Testing Library - Part 2: Configuring Your React Project for Unit Testing

React Unit Testing: A Complete Guide with Jest & Testing Library - Part 2: Configuring Your React Project for Unit Testing
— scroll down — read more

React Unit Testing: A Complete Guide with Jest & Testing Library - Part 2: Configuring Your React Project for Unit Testing

🛠️ React Unit Testing: A Complete Guide with Jest & Testing Library - Part 2: Configuring Your React Project for Unit Testing

📖 8 min read | 💻 Intermediate | ⚡ With Code Examples

   Prerequisites: This guide assumes you have a React project already set up. If you haven't created your React project yet or need help with the initial setup, check out my guide 🚀 React in 2025: Building Modern Apps with Vite - A Developer's Guide, then come back here.

Initial Setup

First, let's install the necessary dependencies for our testing environment:

1npm install --save-dev @testing-library/react@latest @testing-library/jest-dom@latest @testing-library/user-event@latest jest@latest jest-environment-jsdom@latest @babel/preset-react @babel/preset-env babel-jest
2

Project Configuration

Let's set up the essential configuration files:

  1. Jest Configuration
1// jest.config.js
2export default {
3  testEnvironment: "jsdom",
4  setupFilesAfterEnv: ["<rootDir>/src/setupTests.js"],
5  moduleNameMapper: {
6    // Handle CSS imports (with CSS modules)
7    "^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy",
8
9    // Handle CSS imports (without CSS modules)
10    "^.+\\.(css|sass|scss)$": "<rootDir>/__tests__/mocks/styleMock.js",
11
12    // Handle image imports
13    "^.+\\.(jpg|jpeg|png|gif|webp|svg)$":
14      "<rootDir>/__tests__/mocks/fileMock.js",
15
16    // Handle path aliases
17    "^@/(.*)$": "<rootDir>/src/$1",
18  },
19  transform: {
20    "^.+\\.(js|jsx|ts|tsx)$": "babel-jest",
21  },
22  testMatch: [
23    "**/__tests__/**/*.[jt]s?(x)",
24    "**/?(*.)+(spec|test).[jt]s?(x)",
25    "!**/__tests__/mocks/*.[jt]s?(x)",
26    "!**/__tests__/helpers/*.[jt]s?(x)",
27  ],
28  coverageThreshold: {
29    global: {
30      branches: 95,
31      functions: 95,
32      lines: 95,
33      statements: 95,
34    },
35  },
36};
37
  1. Babel Configuration
1// babel.config.json
2{
3  "presets": [
4    "@babel/preset-env",
5    ["@babel/preset-react", { "runtime": "automatic" }]
6  ]
7}
8
  1. Setup Tests
1// src/setupTests.js
2import "@testing-library/jest-dom";
3import { cleanup } from "@testing-library/react";
4
5// Add custom jest matchers
6expect.extend({
7  // Add your custom matchers here
8});
9
10// Global test setup
11beforeAll(() => {
12  // Your global setup code
13});
14
15afterAll(() => {
16  // Your global cleanup code
17  cleanup();
18});
19
  1. Mock files
1// __tests__/mocks/fileMock.js
2export default "test-file-stub";
3
4// __tests__/mocks/styleMock.js
5export default {};
6
7// __tests__/mocks/windowMock.js
8Object.defineProperty(window, "localStorage", {
9  value: {
10    getItem: jest.fn(),
11    setItem: jest.fn(),
12    removeItem: jest.fn(),
13    clear: jest.fn(),
14  },
15});
16

ESLint config

  1. Install jest eslint plugin
1npm install eslint-plugin-jest
2
  1. Add the new plugin in the lint configuration
1// eslint.config.js
2
3// ...others imports
4import pluginJest from "eslint-plugin-jest";
5
6export default [
7  // ...others configs
8  plugins: {
9    // ...others plugins
10    jest: pluginJest,
11  },
12  rules: {
13    // ...others rules
14    "jest/no-disabled-tests": "warn",
15    "jest/no-focused-tests": "error",
16    "jest/no-identical-title": "error",
17    "jest/prefer-to-have-length": "warn",
18    "jest/valid-expect": "error",
19  }
20]
21

Your configuration should look like this:

1import js from "@eslint/js";
2import globals from "globals";
3import react from "eslint-plugin-react";
4import reactHooks from "eslint-plugin-react-hooks";
5import reactRefresh from "eslint-plugin-react-refresh";
6import pluginJest from "eslint-plugin-jest";
7
8export default [
9  { ignores: ["dist"] },
10  {
11    files: ["**/*.{js,jsx}"],
12    languageOptions: {
13      ecmaVersion: 2020,
14      globals: {
15        ...globals.browser,
16        ...pluginJest.environments.globals.globals,
17      },
18      parserOptions: {
19        ecmaVersion: "latest",
20        ecmaFeatures: { jsx: true },
21        sourceType: "module",
22      },
23    },
24    settings: { react: { version: "18.3" } },
25    plugins: {
26      react,
27      "react-hooks": reactHooks,
28      "react-refresh": reactRefresh,
29      jest: pluginJest,
30    },
31    rules: {
32      ...js.configs.recommended.rules,
33      ...react.configs.recommended.rules,
34      ...react.configs["jsx-runtime"].rules,
35      ...reactHooks.configs.recommended.rules,
36      "react/jsx-no-target-blank": "off",
37      "react-refresh/only-export-components": [
38        "warn",
39        { allowConstantExport: true },
40      ],
41      "jest/no-disabled-tests": "warn",
42      "jest/no-focused-tests": "error",
43      "jest/no-identical-title": "error",
44      "jest/prefer-to-have-length": "warn",
45      "jest/valid-expect": "error",
46    },
47  },
48];
49

Package.json Scripts

Add these scripts to your package.json:

1{
2  "scripts": {
3    "test": "jest",
4    "test:watch": "jest --watch",
5    "test:coverage": "jest --coverage",
6    "test:ci": "jest --ci --coverage"
7  }
8}
9

Setting Up Test Files Structure

Here's a recommended structure for your test files:

1src/
2├── components/
3│   ├── TaskManager/
4│   │   ├── TaskManager.jsx
5│   │   ├── TaskManager.test.jsx
6├── __tests__/
7│   ├── helpers/
8│   │   └── test-utils.js
9│   ├── mocks/
10│   │   └── fileMock.js
11│   │   └── styleMock.js
12│   │   └── windowMock.js
13└── setupTests.js
14

Custom Test Utilities

Create helper functions to make testing easier:

1// src/test-utils.jsx
2import { render } from "@testing-library/react";
3import userEvent from "@testing-library/user-event";
4
5const customRender = (ui, options = {}) => {
6  const AllTheProviders = ({ children }) => {
7    return (
8      // Add your providers here (React Query, Redux, etc)
9      children
10    );
11  };
12
13  return {
14    user: userEvent.setup(),
15    ...render(ui, { wrapper: AllTheProviders, ...options }),
16  };
17};
18
19export { customRender as render };
20

Testing Environment Best Practices

  1. Test File Naming Conventions
1// Component test
2Component.test.jsx
3// or
4Component.spec.jsx
5
6// Hook test
7useHook.test.js
8
9// Utility test
10utility.test.js
11
  1. Mocking Common Browser APIs
1// src/__mocks__/windowMock.js
2Object.defineProperty(window, "localStorage", {
3  value: {
4    getItem: jest.fn(),
5    setItem: jest.fn(),
6    removeItem: jest.fn(),
7    clear: jest.fn(),
8  },
9});
10
11Object.defineProperty(window, "matchMedia", {
12  value: jest.fn().mockImplementation((query) => ({
13    matches: false,
14    media: query,
15    onchange: null,
16    addEventListener: jest.fn(),
17    removeEventListener: jest.fn(),
18  })),
19});
20
  1. Common Test Configurations
1// For components that use timeouts
2jest.useFakeTimers();
3
4// For fetch calls
5global.fetch = jest.fn();
6
7// For environment variables
8process.env.REACT_APP_API_URL = "http://test-api.com";
9

CI/CD Integration

For GitHub Actions:

1# .github/workflows/test.yml
2name: Tests
3on: [push, pull_request]
4
5jobs:
6  test:
7    runs-on: ubuntu-latest
8    steps:
9      - uses: actions/checkout@v4
10      - uses: actions/setup-node@v4
11        with:
12          node-version: "20"
13          cache: "npm"
14
15      - name: Install dependencies
16        run: npm ci
17
18      - name: Run tests
19        run: npm run test:ci
20

This configuration provides:

  • Automated dependency installation
  • Test running on push/PR
  • Coverage reporting

Debugging Tests

Add these configurations for VS Code:

1// .vscode/launch.json
2{
3  "version": "0.2.0",
4  "configurations": [
5    {
6      "name": "Debug Jest Tests",
7      "type": "node",
8      "request": "launch",
9      "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/jest",
10      "args": [
11        "--runInBand",
12        "--watchAll=false",
13        "--testNamePattern",
14        "${jest.testNamePattern}",
15        "--runTestsByPath",
16        "${jest.testFile}"
17      ],
18      "cwd": "${workspaceRoot}",
19      "console": "integratedTerminal",
20      "internalConsoleOptions": "neverOpen",
21      "disableOptimisticBPs": true
22    },
23    {
24      "name": "Debug Current Test File",
25      "type": "node",
26      "request": "launch",
27      "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/jest",
28      "args": ["${fileBasename}", "--config", "jest.config.js"],
29      "console": "integratedTerminal",
30      "internalConsoleOptions": "neverOpen"
31    }
32  ]
33}
34

Share this post