Testing basics
An automated test is code that checks other code for you: it clicks a button and compares the result with what is expected. A Next.js app usually has two kinds of tests: unit tests check a single component or function, end-to-end (E2E) tests open the real site in a browser and walk through a whole scenario.
Installing Vitest
Vitest is a fast tool for unit tests; the Testing Library package is installed with it to render components:
npm install -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/dom vite-tsconfig-pathsCreate a settings file in the project root:
// vitest.config.mts
import react from '@vitejs/plugin-react';
import tsconfigPaths from 'vite-tsconfig-paths';
import { defineConfig } from 'vitest/config';
export default defineConfig({
plugins: [tsconfigPaths(), react()],
test: { environment: 'jsdom' },
});jsdom imitates a browser inside Node.js, and tsconfigPaths teaches Vitest to understand paths like @/lib/posts. Add a "test": "vitest" script to package.json.
A component test
Let us test the Like button from the client components lesson. The test file sits next to the component:
// components/LikeButton.test.tsx
import { fireEvent, render, screen } from '@testing-library/react';
import { expect, test } from 'vitest';
import LikeButton from './LikeButton';
test('the counter grows on click', () => {
render(<LikeButton />);
const button = screen.getByRole('button');
fireEvent.click(button);
expect(button.textContent).toBe('Likes: 1');
});render draws the component, screen.getByRole finds the button the way a user would, fireEvent.click clicks it. Run npm test — the terminal shows a green mark next to "the counter grows on click".
An end-to-end test in Playwright
Vitest cannot render asynchronous server components. They are checked with end-to-end tests in a real browser:
npm init playwright@latestThe command installs Playwright and its browsers and creates a folder for tests. A test for the blog:
// e2e/posts.spec.ts
import { expect, test } from '@playwright/test';
test('a post opens from the list', async ({ page }) => {
await page.goto('http://localhost:3000/posts');
await page.getByRole('link', { name: 'Hello, Next.js' }).click();
await expect(page.getByRole('heading', { level: 1 })).toHaveText('Hello, Next.js');
});Start the site with npm run dev, and in another terminal run npx playwright test. Playwright opens /posts, clicks the link and checks the post heading.
What to test with what
| What to check | Tool |
|---|---|
| A form validation function, calculations | Vitest |
| A client component | Vitest and Testing Library |
| A page with data, a form, navigation | Playwright |
Common mistake
Writing a Vitest test for an async page, such as render(<PostsPage />). The test fails: Testing Library does not support asynchronous server components. Move the logic into ordinary functions and test them in Vitest, and check the page as a whole in Playwright.