Jest

Jest is the most established JavaScript test runner in the React ecosystem, and Next.js ships a built-in configuration helper, next/jest, specifically to make Jest work correctly with a Next.js codebase without you having to hand-roll a transform pipeline.

Normally, wiring up Jest for a modern Next.js app means configuring a compiler to transform TSX/JSX, mocking CSS imports (Jest can't parse a .css file), mocking image and font imports, and loading .env files the same way Next.js does. The next/jest helper does all of this automatically, using the same SWC compiler Next.js itself uses, so your test transforms stay consistent with your actual build.

Installation

Install Jest and the supporting testing packages as dev dependencies:

Install the dependencies, then generate the config. createJestConfig is imported from next/jest and wraps your custom Jest settings so that it can inject the Next.js-specific transform, module mapping, and environment setup asynchronously (Next.js needs to load its own config first).

jest.config.js:
const nextJest = require('next/jest')

// Providing the path to your Next.js app lets next/jest load next.config.js
// and .env files, and automatically configure the SWC transform.
const createJestConfig = nextJest({
  dir: './',
})

// Add any custom Jest config you want here -- it will be merged with
// (and takes priority alongside) the Next.js defaults.
const customJestConfig = {
  setupFilesAfterEach: [],
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  testEnvironment: 'jest-environment-jsdom',
  moduleNameMapper: {
    // support the "@/..." import alias if your project uses one
    '^@/(.*)$': '<rootDir>/$1',
  },
}

// createJestConfig is exported this way so that next/jest can load the
// Next.js config, which is async, before Jest runs.
module.exports = createJestConfig(customJestConfig)

jest.setup.js:
// Extends Jest's expect() with DOM-specific matchers such as
// toBeInTheDocument() and toHaveTextContent().
import '@testing-library/jest-dom'

Add a test script to package.json so the suite can be run with npm test:


package.json:
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "test": "jest",
    "test:watch": "jest --watch"
  }
}


Unit Testing a Utility Function

Plain functions -- ones that don't touch React at all -- are the simplest and cheapest thing to test. No rendering or DOM is involved, so these tests run almost instantly.

A small formatting utility and its unit test. Jest test files are conventionally named *.test.ts or placed under a __tests__ folder -- either convention is picked up automatically.

lib,format-price.ts:
export function formatPrice(cents: number, currency = 'USD'): string {
  if (!Number.isFinite(cents)) {
    throw new Error('formatPrice: cents must be a finite number')
  }
  return new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency,
  }).format(cents / 100)
}

lib,format-price.test.ts:
import { formatPrice } from './format-price'

describe('formatPrice', () => {
  it('formats whole dollar amounts', () => {
    expect(formatPrice(2500)).toBe('$25.00')
  })

  it('formats fractional cents correctly', () => {
    expect(formatPrice(1999)).toBe('$19.99')
  })

  it('throws on non-finite input', () => {
    expect(() => formatPrice(NaN)).toThrow('finite number')
  })
})


Testing a Client Component

Jest on its own only runs JavaScript -- it doesn't know how to render a React tree or query the resulting DOM. That's the job of React Testing Library, which is covered in depth on its own page. The example below shows the two working together: Jest provides the runner and the jsdom environment, React Testing Library renders the component and provides the query and interaction APIs.

A simple 'use client' counter component and a Jest + React Testing Library test for it.

components,Counter.tsx:
'use client'

import { useState } from 'react'

export default function Counter() {
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount((c) => c + 1)}>Increment</button>
    </div>
  )
}

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('starts at zero and increments on click', async () => {
    const user = userEvent.setup()
    render(<Counter />)

    expect(screen.getByText('Count: 0')).toBeInTheDocument()

    await user.click(screen.getByRole('button', { name: /increment/i }))

    expect(screen.getByText('Count: 1')).toBeInTheDocument()
  })
})


What Jest Can't Do

Because Jest tests run in Node.js against jsdom, they never start an actual Next.js server. Async Server Components, Server Actions invoked over a real request, route handlers, and middleware are outside what Jest (with or without React Testing Library) can exercise directly. For those, use Playwright to test against a real running app.

If your project prioritizes faster test runs and closer alignment with a Vite-based toolchain, Vitest is a drop-in-flavored alternative to Jest that Next.js also officially supports.