Part 5.2 — Setting Up Jest, Testing Library & Test Environment
Before you write a single frontend test, your tooling needs to disappear into the background.
If setup is fragile or confusing, developers avoid testing altogether.
In this post, you’ll set up a clean, predictable Jest environment for a React + TypeScript app—one that mirrors real usage without leaking implementation details.
1. Install Testing Dependencies
From your React project root:
pnpm add -D jest @types/jest ts-jest @testing-library/react @testing-library/jest-dom @testing-library/user-eventThese give you:
Jest test runner
DOM testing helpers
realistic user interaction simulation
2. Configure Jest for TypeScript + React
Create jest.config.ts:
import type { Config } from 'jest';
const config: Config = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/test/setup.ts'],
moduleFileExtensions: ['ts', 'tsx', 'js'],
testMatch: ['**/*.spec.tsx', '**/*.test.tsx'],
transform: {
'^.+\\.(ts|tsx)$': 'ts-jest',
},
};
export default config;This configuration is explicit and predictable.
3. Add Global Test Setup
Create src/test/setup.ts:
import '@testing-library/jest-dom';This adds matchers like:
expect(button).toBeDisabled();
expect(text).toBeInTheDocument();These match how users perceive the UI.
4. Configure Module Aliases (If Used)
If your app uses path aliases:
import Button from '@/components/Button';Map them in Jest:
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},Add this inside jest.config.ts.
5. Mock Browser APIs Missing in jsdom
jsdom doesn’t implement everything.
Common mocks:
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(() => ({
matches: false,
addListener: jest.fn(),
removeListener: jest.fn(),
})),
});Put these in setup.ts.
Only mock what breaks your tests—nothing more.
6. Mock fetch Globally
Your React app talks to REST APIs.
Tests should not.
Add to setup.ts:
global.fetch = jest.fn();Later, you’ll mock responses per test.
7. Environment Variables for Tests
Create .env.test:
VITE_API_URL=http://localhost:4000Tell Jest to load it:
import 'dotenv/config';Place this at the top of setup.ts.
This ensures your API layer behaves consistently.
8. Add pnpm Test Scripts
In package.json:
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:ci": "jest --runInBand"
}These mirror backend testing conventions.
9. Verify Setup with a Simple Test
Create App.test.tsx:
import { render, screen } from '@testing-library/react';
import App from './App';
it('renders the app', () => {
render(<App />);
expect(screen.getByText(/welcome/i)).toBeInTheDocument();
});Run:
pnpm testIf this passes, your environment is ready.
10. Common Setup Pitfalls
If tests fail immediately, check:
missing jsdom environment
incorrect testMatch patterns
path alias mismatches
unmocked browser APIs
fetch not mocked
environment variables missing
Fix these once—never repeatedly per test.
11. Keep Setup Boring
The best test setup is:
short
boring
invisible
Avoid clever abstractions here.
The complexity belongs in tests—not configuration.
12. Summary
You now have:
Jest configured for React + TypeScript
React Testing Library wired correctly
global DOM matchers
fetch mocking
environment variable support
pnpm scripts aligned with CI
In Part 5.3, you’ll write real component and hook tests—covering UI state, async flows, and user interactions the right way.
Related
Part 6.6 — Async State, Side Effects & Server State Boundaries
Most React state bugs come from mixing UI state with server state. This post teaches you how to draw a hard line between them—and why that line matters.
Part 6.5 — Introducing Redux Toolkit the Right Way
Redux isn’t the default anymore—but when your app needs it, Redux Toolkit is the cleanest, safest way to introduce global state at scale.
Part 6.4 — Combining Context + useReducer for App-Level State
Context provides access. Reducers provide structure. Together, they form a powerful, lightweight architecture for managing app-level state in React.
Comments