Learning Next.js - Testing & Quality
Episode 16 of 24

Learning Next.js - Testing & Quality

This episode covers unit testing with Jest and React Testing Library, integration and E2E testing with Playwright or Cypress, accessibility testing, and static code analysis with ESLint and type checking to maintain quality.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Untested code is a time bomb. A small change can break a feature you never imagined, and new bugs appear unnoticed until a user reports them. Testing changes that process: every regression is detected automatically before reaching production.

Episode 16 covers unit testing with Jest and React Testing Library, integration and E2E testing with Playwright or Cypress, accessibility testing, and static analysis with ESLint and type checking.

Unit Testing with Jest and React Testing Library

Setup and Your First Test

Jest is the most popular test runner for JavaScript, paired with React Testing Library to render and test components. Install both:

Install Jest and Testing Library
npm install --save-dev jest jest-environment-jsdom @testing-library/react @testing-library/jest-dom @swc/core

Once the configuration is done, here's a unit test for the Counter component from episode 7:

Unit test for the Counter component
import { render, screen } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import Counter from "@/components/Counter"
 
test("tombol Tambah menaikkan hitungan", async () => {
  render(<Counter />)
  await userEvent.click(screen.getByRole("button", { name: "Tambah" }))
  expect(screen.getByText("Hitungan: 1")).toBeInTheDocument()
})

The test above renders the component, clicks the button, and checks the resulting text. Accessing elements via getByRole instead of arbitrary text ensures the component stays accessible to screen readers.

Some habits that keep unit tests high quality: use userEvent instead of fireEvent for realistic simulation, mock fetch with MSW so tests don't depend on the network, and focus on behavior visible to the user rather than implementation details. Run unit tests in watch mode during development — every code change immediately triggers related tests, closing the feedback loop quickly.

Focus on Behavior

React Testing Library encourages testing from the user's point of view: find elements by role and visible text, not by internal implementation. Tests that write implementation details break easily during refactoring, while behavior tests stay relevant as long as the component's function doesn't change.

Integration Testing and E2E with Playwright

E2E in a Real Browser

E2E (end-to-end) tests complete flows in a real browser: navigation, login, form filling, up to the destination page. Playwright is the modern choice that supports many browsers and auto-wait:

E2E test with Playwright
import { test, expect } from "@playwright/test"
 
test("pengguna bisa login", async ({ page }) => {
  await page.goto("/login")
  await page.fill("#email", "user@example.com")
  await page.fill("#password", "rahasia123")
  await page.click("button[type=submit]")
  await expect(page).toHaveURL(/\/dashboard/)
})

page.fill and page.click above simulate a real user. The toHaveURL assertion ensures that after login, the user arrives at the dashboard.

Division of Responsibilities

The testing pyramid: many fast unit tests, some integration tests, and a few E2E tests. Unit tests run in milliseconds and give instant feedback; E2E tests are slow and expensive, so they're used for critical flows like checkout and authentication. This balance keeps the suite fast and meaningful.

Choose E2E for flows involving many systems: login, payments, or data synchronization between pages. For other parts, integration tests that render components with mock data are faster and more stable than E2E. For projects with many teams, split the E2E suite into a separate workflow that runs in parallel in CI — Playwright supports sharding so total time drops drastically.

Stable E2E requires selectors that don't change when the layout changes — use the data-testid attribute with discipline for the elements under test.

Accessibility Testing

Automated and Manual

Accessibility testing ensures the application can be used by everyone, including screen reader users. Automate as much as possible: jest-axe checks contrast and attribute issues in unit tests, while Playwright provides page-level accessibility checks. But automation doesn't replace manual testing with a keyboard and screen reader — some issues only show up when actually navigating.

Start with a basic checklist: all interactive elements are keyboard-accessible, text contrast meets WCAG standards, and every image has alt text. Fix issues found before adding new features — deferred accessibility rarely gets done.

Make accessibility testing part of each feature's definition of done, not a separate task at the end of a sprint. This prevents an accumulation of accessibility debt that's hard to clean up.

Static Code Analysis with ESLint and Type Checking

Making It Part of the Workflow

ESLint and TypeScript catch errors before tests even run. Set both up to run in the pipeline:

Lint and type check in CI
npm run lint
npx tsc --noEmit

Run npm run lint and npx tsc --noEmit on every push and pull request. Combine with husky and lint-staged from episode 3 so quality issues are detected before a commit enters history.

Make type checking part of the editor by running tsc in watch mode, or rely on TypeScript integration in VS Code. Errors appear while typing instead of waiting for CI — much faster and more comfortable feedback.

Treat accumulating warnings like bugs: if lint runs with a hundred warnings, the rule of thumb is to clean them up or disable them deliberately — don't let them pile up.

Closing

Here's what to take away:

  • Unit tests with Jest and RTL focus on user behavior.
  • E2E with Playwright tests complete flows in a real browser.
  • The testing pyramid balances speed and coverage.
  • Automated accessibility testing complements manual testing.
  • ESLint and tsc catch issues before tests run.
  • Test automation in CI maintains long-term quality.

In the next episode, episode 17, we'll discuss SEO and content strategy — SEO fundamentals for Next.js, meta tags and Open Graph with structured data, sitemap generation and robots configuration, and content-driven pages with performance-first SEO. Your application's visibility in search engines will move up a level.

Learning Next.js - Testing & Quality | Learn Next.js