Jest Setup with Next.js
Configure Jest with Next.js using the official next/jest preset for zero-config TypeScript and path alias support.
Recipe
Quick-reference recipe card -- copy-paste ready.
# Install dependencies
npm install -D jest @testing-library/react @testing-library/jest-dom @testing-library/user-event jest-environment-jsdom ts-node @types/jest// jest.config.ts
import type { Config } from "jest";
import nextJest from "next/jest";
const createJestConfig = nextJest({
dir: "./", // Path to your Next.js app (loads next.config.js and .env)
});
const config: Config = {
testEnvironment: "jsdom",
setupFilesAfterSetup: ["<rootDir>/jest.setup.ts"],
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/src/$1",
},
};
export default createJestConfig(config);// jest.setup.ts
import "@testing-library/jest-dom";// package.json scripts
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}When to reach for this: When you have an existing Jest setup, need Jest-specific plugins, or prefer the mature Jest ecosystem with full Next.js integration via next/jest.
Working Example
// src/components/counter.tsx
"use client";
import { useState } from "react";
export function Counter({ initial = 0 }: { initial?: number }) {
const [count, setCount] = useState(initial);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount((c) => c + 1)}>Increment</button>
<button onClick={() => setCount((c) => c - 1)}>Decrement</button>
</div>
);
}// src/components/counter.test.tsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Counter } from "./counter";
describe("Counter", () => {
it("renders with initial count", () => {
render(<Counter initial={5} />);
expect(screen.getByText("Count: 5")).toBeInTheDocument();
});
it("increments on click", async () => {
const user = userEvent.setup();
render(<Counter />);
await user.click(screen.getByRole("button", { name: "Increment" }));
expect(screen.getByText("Count: 1")).toBeInTheDocument();
});
it("decrements on click", async () => {
const user = userEvent.setup();
render(<Counter initial={3} />);
await user.click(screen.getByRole("button", { name: "Decrement" }));
expect(screen.getByText("Count: 2")).toBeInTheDocument();
});
});What this demonstrates:
next/jesthandles TypeScript transforms and CSS module mocking automaticallyuserEvent.setup()for realistic user interaction simulation- Querying by accessible role and text
Deep Dive
How It Works
next/jestcreates a Jest config that integrates with Next.js's SWC compiler for fast transforms (no Babel needed)- It auto-mocks CSS modules, image imports, and
next/fontso they do not break tests - The
diroption tells Next.js where to findnext.config.jsto load environment variables and module aliases setupFilesAfterSetupruns after the test framework is installed in the environment -- this is where you load@testing-library/jest-dommatchersmoduleNameMapperreplicatestsconfig.jsonpath aliases so@/components/...resolves correctly
Variations
Transform config for specific file types:
// jest.config.ts — next/jest handles most transforms, but you can add custom ones
const config: Config = {
transform: {
"\\.svg$": "<rootDir>/test/svg-transform.ts",
},
};Running a subset of tests:
# Run tests matching a pattern
jest --testPathPattern="components"
# Run a single file
jest src/components/counter.test.tsx
# Run tests matching a name
jest -t "increments"Collecting coverage:
// jest.config.ts
const config: Config = {
collectCoverageFrom: [
"src/**/*.{ts,tsx}",
"!src/**/*.d.ts",
"!src/**/index.ts",
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
};TypeScript Notes
// If using ts-node for jest.config.ts, ensure tsconfig allows it
// tsconfig.json
{
"compilerOptions": {
"types": ["jest", "@testing-library/jest-dom"]
}
}
// For ESM projects, you may need:
// jest.config.ts
// export default createJestConfig(config) — next/jest handles ESM interopGotchas
-
next/jestrequires Next.js 13.1+ -- Older versions lack the SWC-based Jest integration. Fix: Upgrade Next.js or configure Babel manually. -
CSS import errors -- If you see
SyntaxError: Unexpected tokenon.cssfiles,next/jestis not applied correctly. Fix: EnsurecreateJestConfigwraps your config anddirpoints to your project root. -
ESM packages failing -- Some npm packages ship ESM-only and Jest defaults to CommonJS. Fix: Add them to
transformIgnorePatterns:const config: Config = { transformIgnorePatterns: [ "node_modules/(?!(swiper|ky|nanoid)/)", ], }; -
Slow test startup -- Jest has higher startup cost than Vitest due to module resolution. Fix: Use
--watchmode during development to cache transforms across runs. -
setupFilesAfterSetupvssetupFiles--setupFilesruns before the test framework loads,setupFilesAfterSetupruns after. Jest-dom matchers must go insetupFilesAfterSetup. Fix: UsesetupFilesAfterSetupfor@testing-library/jest-dom.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Vitest | You want faster watch mode, native ESM, and Vite-based transforms | You need Jest-specific plugins or have a large existing Jest suite |
@swc/jest | You need SWC transforms without the Next.js preset | You are using Next.js (use next/jest instead) |
| Bun test | You use Bun as your runtime and want built-in testing | You need the Jest ecosystem of matchers and plugins |
FAQs
What does next/jest do that a plain Jest config does not?
- It integrates with Next.js's SWC compiler for fast TypeScript transforms.
- It auto-mocks CSS modules, image imports, and
next/font. - It loads
next.config.jsfor environment variables and module aliases.
What is the difference between setupFiles and setupFilesAfterSetup?
setupFilesruns before the test framework loads.setupFilesAfterSetupruns after. Jest-dom matchers must go insetupFilesAfterSetupbecause they extend Jest'sexpect.
Why do I get SyntaxError: Unexpected token on CSS imports?
This means next/jest is not applied correctly. Ensure createJestConfig wraps your config and the dir option points to your project root.
How do I handle ESM-only npm packages in Jest?
Add them to transformIgnorePatterns:
transformIgnorePatterns: [
"node_modules/(?!(swiper|ky|nanoid)/)",
],Why is Jest startup slow compared to Vitest?
- Jest has higher module resolution and transform overhead.
- Use
--watchmode during development to cache transforms across runs. - Consider Vitest if startup time is a critical concern.
Gotcha: What happens if next/jest is used with Next.js below version 13.1?
It will not work. The SWC-based Jest integration requires Next.js 13.1+. For older versions, configure Babel manually.
How do I run only tests matching a specific name?
jest -t "increments"This runs only test cases whose names match the string "increments".
How do I set up coverage thresholds in Jest?
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},How do I add TypeScript types for Jest globals and jest-dom matchers?
Add both to your tsconfig.json:
{
"compilerOptions": {
"types": ["jest", "@testing-library/jest-dom"]
}
}Gotcha: Why does moduleNameMapper not automatically read from tsconfig.json?
Jest does not parse tsconfig.json path aliases. You must replicate them in moduleNameMapper or use next/jest which handles some alias resolution via the dir option.
Can I use custom transforms for specific file types like SVG?
Yes. Add a transform entry in your Jest config:
transform: {
"\\.svg$": "<rootDir>/test/svg-transform.ts",
},What is the recommended way to type jest.config.ts?
Import the Config type from Jest:
import type { Config } from "jest";
const config: Config = { /* ... */ };Related
- Vitest Setup with Next.js -- the faster alternative
- React Testing Library Fundamentals -- querying and asserting
- Testing Components -- patterns for component tests
- Mocking in Tests -- mocking modules and APIs