React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

jestsetupnext.jstestingtesting-library

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/jest handles TypeScript transforms and CSS module mocking automatically
  • userEvent.setup() for realistic user interaction simulation
  • Querying by accessible role and text

Deep Dive

How It Works

  • next/jest creates 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/font so they do not break tests
  • The dir option tells Next.js where to find next.config.js to load environment variables and module aliases
  • setupFilesAfterSetup runs after the test framework is installed in the environment -- this is where you load @testing-library/jest-dom matchers
  • moduleNameMapper replicates tsconfig.json path 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 interop

Gotchas

  • next/jest requires 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 token on .css files, next/jest is not applied correctly. Fix: Ensure createJestConfig wraps your config and dir points 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 --watch mode during development to cache transforms across runs.

  • setupFilesAfterSetup vs setupFiles -- setupFiles runs before the test framework loads, setupFilesAfterSetup runs after. Jest-dom matchers must go in setupFilesAfterSetup. Fix: Use setupFilesAfterSetup for @testing-library/jest-dom.

Alternatives

AlternativeUse WhenDon't Use When
VitestYou want faster watch mode, native ESM, and Vite-based transformsYou need Jest-specific plugins or have a large existing Jest suite
@swc/jestYou need SWC transforms without the Next.js presetYou are using Next.js (use next/jest instead)
Bun testYou use Bun as your runtime and want built-in testingYou 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.js for environment variables and module aliases.
What is the difference between setupFiles and setupFilesAfterSetup?
  • setupFiles runs before the test framework loads.
  • setupFilesAfterSetup runs after. Jest-dom matchers must go in setupFilesAfterSetup because they extend Jest's expect.
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 --watch mode 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 = { /* ... */ };