Skip to main content
Next.js supports several testing approaches, each serving different purposes:
TypePurposeRecommended tools
UnitTest individual functions, hooks, or components in isolationJest, Vitest
ComponentTest React component rendering and interactionsJest + RTL, Vitest + RTL
IntegrationTest how multiple units work togetherJest, Vitest
End-to-endTest real user flows in a browser environmentPlaywright, Cypress
SnapshotCapture rendered output to catch unexpected UI changesJest, Vitest
async Server Components are new to the React ecosystem and not fully supported by Jest or Vitest for unit testing. Use end-to-end testing for async Server Components.

Jest

Jest and React Testing Library are the standard for unit and snapshot testing in Next.js.

Setup

Use create-next-app with the Jest example:
npx create-next-app@latest --example with-jest with-jest-app

Custom matchers

Add @testing-library/jest-dom custom matchers like .toBeInTheDocument():
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts']
import '@testing-library/jest-dom'

Writing tests

Add test scripts to package.json:
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch"
  }
}
Create a __tests__ directory in your project root and write your first test:
import '@testing-library/jest-dom'
import { render, screen } from '@testing-library/react'
import Page from '../app/page'

describe('Page', () => {
  it('renders a heading', () => {
    render(<Page />)
    const heading = screen.getByRole('heading', { level: 1 })
    expect(heading).toBeInTheDocument()
  })
})
Add a snapshot test to catch unexpected UI changes:
import { render } from '@testing-library/react'
import Page from '../app/page'

it('renders homepage unchanged', () => {
  const { container } = render(<Page />)
  expect(container).toMatchSnapshot()
})

Module path aliases

If you use path aliases in tsconfig.json, configure moduleNameMapper in Jest:
{
  "compilerOptions": {
    "paths": {
      "@/components/*": ["components/*"]
    }
  }
}
moduleNameMapper: {
  '^@/components/(.*)$': '<rootDir>/components/$1',
}

Vitest

Vitest offers faster execution and native ESM support, making it a good alternative to Jest.

Setup

npx create-next-app@latest --example with-vitest with-vitest-app

Writing Vitest tests

import { expect, test } from 'vitest'
import { render, screen } from '@testing-library/react'
import Page from '../app/page'

test('Page', () => {
  render(<Page />)
  expect(screen.getByRole('heading', { level: 1, name: 'Home' })).toBeDefined()
})
Vitest watches for changes by default when you run npm run test.

Playwright

Playwright enables end-to-end testing across Chromium, Firefox, and WebKit.

Setup

npx create-next-app@latest --example with-playwright with-playwright-app

Writing E2E tests

Create pages to test:
import Link from 'next/link'

export default function Page() {
  return (
    <div>
      <h1>Home</h1>
      <Link href="/about">About</Link>
    </div>
  )
}
Write a test to verify navigation:
import { test, expect } from '@playwright/test'

test('should navigate to the about page', async ({ page }) => {
  await page.goto('http://localhost:3000/')
  await page.click('text=About')
  await expect(page).toHaveURL('http://localhost:3000/about')
  await expect(page.locator('h1')).toContainText('About')
})

Running Playwright tests

Build and start your app, then run tests:
npm run build && npm run start
npx playwright test
For CI, install browser dependencies first:
npx playwright install-deps

Cypress

Cypress supports both end-to-end and component testing.

Setup

npx create-next-app@latest --example with-cypress with-cypress-app

Writing Cypress E2E tests

describe('Navigation', () => {
  it('should navigate to the about page', () => {
    cy.visit('http://localhost:3000/')
    cy.get('a[href*="about"]').click()
    cy.url().should('include', '/about')
    cy.get('h1').contains('About')
  })
})
Run tests against your production build:
npm run build && npm run start
npm run cypress:open

Cypress component tests

Cypress can also mount and test individual React components without a running server:
import { defineConfig } from 'cypress'

export default defineConfig({
  component: {
    devServer: {
      framework: 'next',
      bundler: 'webpack',
    },
  },
})
import Page from '../../app/page'

describe('<Page />', () => {
  it('should render and display expected content', () => {
    cy.mount(<Page />)
    cy.get('h1').contains('Home')
    cy.get('a[href="/about"]').should('be.visible')
  })
})

CI configuration

Run Cypress headlessly in CI:
{
  "scripts": {
    "e2e:headless": "start-server-and-test dev http://localhost:3000 \"cypress run --e2e\"",
    "component:headless": "cypress run --component"
  }
}

Build docs developers (and LLMs) love