Test Next.js apps with Jest, React Testing Library, and E2E testing with Playwright or Cypress.
npm install --save-dev jest jest-environment-jsdom @testing-library/react @testing-library/jest-domjest.config.js using the built-in Next.js Jest preset.const nextJest = require("next/jest");
const createJestConfig = nextJest({ dir: "./" });
module.exports = createJestConfig({ testEnvironment: "jsdom" });.test.js or .spec.js extensions.// utils.test.js
import { add } from "./utils";
it("adds two numbers", () => {
expect(add(2, 3)).toBe(5);
});npm test -- --watchdescribe to group related tests and it for individual test cases. Jest runs tests in parallel by default.import { render, screen } from "@testing-library/react";
import Button from "./Button";
it("renders a button", () => {
render(<Button>Click me</Button>);
const btn = screen.getByRole("button", { name: /click me/i });
expect(btn).toBeInTheDocument();
});getByRole, getByText.screen.getByRole("button", { name: /submit/i });
screen.getByLabelText("Email");
screen.getByText("Welcome");userEvent instead of fireEvent.import userEvent from "@testing-library/user-event";
const user = userEvent.setup();
await user.click(button);import { createMocks } from "node-mocks-http";
import handler from "./api/hello";
it("returns a greeting", async () => {
const { req, res } = createMocks({
method: "GET"
});
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(JSON.parse(res._getData())).toHaveProperty("message");
});node-mocks-http to mock request and response.jest.mock("./api", () => ({
fetchUser: jest.fn(() => Promise.resolve({ id: 1, name: "Alice" }))
}));const mockUser = { id: 1, name: "Alice", email: "alice@example.com" };
it("displays user info", () => {
render(<Profile user={mockUser} />);
expect(screen.getByText("Alice")).toBeInTheDocument();
});useRouter or usePathname.jest.mock("next/navigation", () => ({
useRouter: jest.fn(() => ({ push: jest.fn(), replace: jest.fn() })),
usePathname: jest.fn(() => "/"),
useSearchParams: jest.fn(() => new URLSearchParams())
}));// playwright.spec.ts
import { test, expect } from "@playwright/test";
test("user can sign up", async ({ page }) => {
await page.goto("http://localhost:3000");
await page.fill("input[name=email]", "test@example.com");
await page.click("button:has-text('Sign Up')");
await expect(page).toHaveURL("/dashboard");
});npx playwright testInstall jest, jest-environment-jsdom, @testing-library/react, and @testing-library/jest-dom, then add a jest.config.js using Next.js's built-in preset: const nextJest = require('next/jest'); module.exports = nextJest({ dir: './' })({testEnvironment: 'jsdom'}).
Both work well, but Playwright is generally preferred for new projects due to faster execution, built-in multi-browser support, and better handling of Next.js server components. Cypress has a more mature ecosystem and better real-time debugging.
Import the handler function directly and call it with mocked req/res objects using libraries like node-mocks-http, then assert on res.status() and res.json() values without spinning up a server.
This usually means the element is conditionally rendered or loaded asynchronously—use async queries like findByText or waitFor instead of getByText so the test waits for the DOM to update after data fetching or state changes.
Mock the next/navigation module (App Router) or next/router (Pages Router) using jest.mock: jest.mock('next/navigation', () => ({ useRouter: () => ({ push: jest.fn(), pathname: '/' }) })) at the top of your test file.