Next.js Testing Basics

Every change to an application carries a small risk. A tweak to a price calculator might break the checkout page. A renamed prop might empty a product card. Automated tests catch these problems in seconds, before visitors ever see them. Testing turns fear of change into confidence.

The Everyday View

Think of a pilot's checklist. The pilot knows how to fly, yet a checklist confirms that fuel, brakes, and controls work before every takeoff. Automated tests serve as that checklist for your code. Each run confirms that the important parts still behave as expected.

Types of Tests

Three test types cover most needs. Each one trades speed against realism.

              /\
             /  \        End-to-end tests
            /    \       (few, slow, realistic)
           /------\
          /        \     Integration tests
         /          \    (some, medium)
        /------------\
       /              \  Unit tests
      /                \ (many, fast, small)
     /------------------\

Unit Tests

A unit test checks one small piece, such as a function that formats a price. These tests run in milliseconds and pinpoint failures precisely.

Integration Tests

An integration test checks several pieces together, such as a form component and its validation helper. The test confirms that the parts cooperate.

End-to-End Tests

An end-to-end test drives a real browser. The script opens the site, clicks buttons, types text, and checks the screen. These tests give the most realistic picture and take the longest time.

Common Tools

ToolPurpose
Jest or VitestTest runner for unit and integration tests
React Testing LibraryRenders components and queries the screen the way a user sees it
Playwright or CypressBrowser automation for end-to-end tests

Next.js documentation provides setup guides for each tool. Install the packages that match your choice and follow the steps for your version.

A First Unit Test

Start with a plain function. This helper turns a number into a price label.

// lib/formatPrice.js
export function formatPrice(amount) {
  return '$' + amount.toFixed(2);
}

The matching test lives in a file that ends with .test.js.

// lib/formatPrice.test.js
import { describe, it, expect } from 'vitest';
import { formatPrice } from './formatPrice';

describe('formatPrice', () => {
  it('adds a dollar sign and two decimals', () => {
    expect(formatPrice(5)).toBe('$5.00');
  });

  it('rounds long decimals', () => {
    expect(formatPrice(9.999)).toBe('$10.00');
  });
});

The describe block groups related checks. Each it block states one expected behavior in plain language. The expect call compares the real result with the target.

Testing a Component

React Testing Library renders a component inside a simulated page. Queries find elements by the text and roles that a visitor sees.

// components/Greeting.js
export default function Greeting({ name }) {
  return <h1>Welcome, {name}</h1>;
}
// components/Greeting.test.js
import { render, screen } from '@testing-library/react';
import { it, expect } from 'vitest';
import Greeting from './Greeting';

it('shows the visitor name', () => {
  render(<Greeting name="Asha" />);
  expect(screen.getByText('Welcome, Asha')).toBeTruthy();
});

Searching by visible text keeps tests close to real behavior. A rewrite of the internal code does not break the test as long as the screen output stays the same.

A First End-to-End Test

// e2e/home.spec.js
import { test, expect } from '@playwright/test';

test('home page shows the main heading', async ({ page }) => {
  await page.goto('http://localhost:3000');
  await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
});

Start the dev server before the run, or configure the tool to start it automatically.

Async Server Components

Unit test tools struggle with async Server Components because those components run on the server and await data. The official guidance recommends end-to-end tests for such components. Move reusable logic into plain functions and test those functions directly.

Running Tests

Add a script to package.json so the whole team uses the same command.

{
  "scripts": {
    "test": "vitest",
    "test:e2e": "playwright test"
  }
}

Run npm test during development and in your deployment pipeline. A failing test should stop a release.

What to Test First

  • Price, tax, and discount calculations.
  • Form validation rules.
  • Sign-in and sign-out paths.
  • The checkout or signup journey.
  • Bugs you have fixed before, to prevent their return.

Good Testing Habits

  • Name each test after the behavior, not the code.
  • Keep each test focused on one idea.
  • Avoid testing library internals.
  • Run tests often so failures stay easy to trace.
  • Fix or delete flaky tests quickly.

Common Mistakes

Chasing Full Coverage

A high percentage does not guarantee quality. Cover the paths that matter most to visitors.

Testing Everything in the Browser

Browser tests run slowly. Use them for key journeys and rely on unit tests for details.

Quick Summary

Unit tests check small pieces, integration tests check teamwork, and end-to-end tests check whole journeys. Start with a few tests on the most important logic and grow the suite as the app grows.