Next.js cookbook · Intermediate
Test a Next.js app with Vitest and Playwright
Set up fast component tests and a few end-to-end tests for critical flows in a Next.js project.
Approach
Use Vitest + Testing Library (jsdom) for Client Components and pure functions, test Server Actions/data functions as plain async functions with mocked I/O, and use Playwright against next build && next start for end-to-end flows (async Server Components are best covered by E2E tests).
Solution
// vitest.config.mts
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({ plugins: [tsconfigPaths(), react()], test: { environment: "jsdom", setupFiles: ["./vitest.setup.ts"] } });
// components/ThemeToggle.test.tsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
it("lets the user pick dark mode", async () => {
render(<ThemeToggle />);
await userEvent.selectOptions(await screen.findByLabelText("Theme"), "dark");
expect(screen.getByLabelText("Theme")).toHaveValue("dark");
});
// e2e/checkout.spec.ts
import { test, expect } from "@playwright/test";
test("guest can check out", async ({ page }) => {
await page.goto("/products/blue-mug");
await page.getByRole("button", { name: "Add to cart" }).click();
await page.getByRole("link", { name: /cart/i }).click();
await page.getByRole("button", { name: "Checkout" }).click();
await expect(page.getByRole("heading", { name: "Order confirmed" })).toBeVisible();
});
// playwright.config.ts (excerpt)
export default defineConfig({
webServer: { command: "npm run build && npm run start", url: "http://localhost:3000", reuseExistingServer: !process.env.CI },
use: { baseURL: "http://localhost:3000", trace: "on-first-retry" },
});
Pitfalls
- Trying to unit test async Server Components with jsdom (limited support) — use E2E.
- E2E tests against
next dev(slower, different behavior). - Shared test accounts causing flaky E2E runs.
Variations
- MSW to mock external APIs in both Vitest and Playwright.
Further reading: Next.js: Testing
Reference: https://nextjs.org/docs/app/guides/testing
More Next.js recipes
- Add a Content Security Policy with nonces in Next.js
- Add dark mode to Next.js without a flash
- Add SEO metadata, Open Graph and sitemaps
- Avoid data-fetching waterfalls in Server Components
- Build a validated JSON API with Route Handlers
- Create encrypted session cookies in Next.js
- Handle errors and 404s in the App Router
- Handle forms with Server Actions and validation