Skip to main content
Testing & Quality · Blog 31

Next.js 16 Testing with Vitest and React Testing Library

Build fast, user-centered tests for utilities, components, forms, asynchronous UI, mocks and continuous integration—without confusing unit tests with real-browser confidence.

A browser application, code editor and testing pipeline with successful quality checks

Building features is only one part of production development. Tests help detect regressions before users encounter them. We have already learned how to build, deploy, scale and secure a production Next.js application across Blogs #1–#30. The next step is proving that the application still behaves as expected whenever its code changes.

Next.js Vitest testing provides a fast feedback loop for JavaScript and TypeScript logic, synchronous React components, Client Component behavior and focused integration boundaries. React Testing Library adds user-centered DOM queries and interaction helpers. Together they cover important ground, but they do not replace end-to-end tests in a real browser.

How Do You Test a Next.js App with Vitest?

Next.js Testing at a Glance

Choose the smallest test that gives credible evidence. Unit tests isolate rules. Component tests render UI. Integration tests combine meaningful pieces. End-to-end tests exercise the deployed behavior through a browser. The familiar testing pyramid is a useful cost model, not a law; risk and architecture should determine the final mix.

Diagram 1: A practical testing pyramidA broad base of fast unit and component checks supports fewer, broader integration and end-to-end flows.
E2E
real workflows
Integration
pieces together
Component tests
UI behavior
Unit tests
fast, focused logic
Diagram 2: The Next.js testing stackUse the testing level that best matches the behavior you need to verify.
Utilities
Vitest
React components
Vitest + RTL
Complete browser flows
Playwright

Types of Tests

TypePurposeExample
UnitSmall isolated logicformatPrice
ComponentReact UI behaviorCounter
IntegrationMultiple pieces togetherForm + validation
E2EFull user workflowSignup

Use Vitest for fast unit, component and focused integration tests. Use Playwright or another supported E2E tool for navigation, browser APIs, deployment behavior and complete journeys.

NeedVitestPlaywright
Utility functionExcellentUsually unnecessary
Client ComponentExcellentPossible
Synchronous componentGoodGood
Async Server ComponentLimitedRecommended
Full browser navigationNoYes
Authentication flowLimitedExcellent
Complete checkout or signupNoExcellent

Install and Configure Vitest

First inspect the real application. Preserve its package manager, scripts, path aliases and existing test tools. For an npm TypeScript project with no test setup, the current Next.js guide lists Vitest, the React Vite plugin, jsdom, React Testing Library, DOM Testing Library and vite-tsconfig-paths as development dependencies. Add @testing-library/user-event only when realistic interaction helpers are useful, and @testing-library/jest-dom only when its DOM matchers are intentionally configured.

npm install -D vitest @vitejs/plugin-react jsdom \
  @testing-library/react @testing-library/dom vite-tsconfig-paths \
  @testing-library/user-event @testing-library/jest-dom
// 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: ['./src/test/setup.ts'],
    restoreMocks: true,
  },
})
// src/test/setup.ts
import '@testing-library/jest-dom/vitest'

vite-tsconfig-paths helps Vitest resolve aliases declared in tsconfig.json. Confirm that every alias also maps to a real directory. jsdom supplies many DOM APIs inside Node; it is useful for component tests but does not reproduce layout, rendering, navigation and browser engines completely.

Diagram 3: Component test environmentThe runner loads the test in jsdom; React Testing Library renders the component and exposes its user-visible DOM.
Test fileVitestjsdomReact Testing LibraryRendered component

Keep scripts explicit: "test": "vitest" for watch mode and "test:run": "vitest run" for CI. Do not add a coverage command until a provider is installed and configured. Tests may live in a shared __tests__ directory or beside their source as *.test.ts and *.test.tsx; use the repository’s established convention consistently.

Your First Unit Test

Pure business logic is inexpensive to test because it needs no browser, database or framework lifecycle. Arrange the data, act by calling the function, and assert the returned result.

// format-price.ts
export function formatPrice(price: number) {
  return `€${price.toFixed(2)}`
}

// format-price.test.ts
import { describe, expect, it } from 'vitest'
import { formatPrice } from './format-price'

describe('formatPrice', () => {
  it('formats a price with two decimals', () => {
    expect(formatPrice(10)).toBe('€10.00')
  })
})
Diagram 4: Arrange, Act, AssertA readable test prepares inputs, runs one behavior, and verifies the meaningful result.
Arrange
prepare data
Act
run code
Assert
verify result

Component Testing and User Behavior

React Testing Library’s central idea is to test what a user can observe instead of internal React state. A stable test clicks an accessible control and verifies changed content. It does not inspect useState, private variables or component-instance methods.

'use client'
import { useState } from 'react'

export function Counter() {
  const [count, setCount] = useState(0)
  return (<>
    <p>Count: {count}</p>
    <button onClick={() => setCount(count + 1)}>Increase</button>
  </>)
}
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { expect, it } from 'vitest'
import { Counter } from './Counter'

it('increases the visible count', async () => {
  const user = userEvent.setup()
  render(<Counter />)
  expect(screen.getByText('Count: 0')).toBeInTheDocument()
  await user.click(screen.getByRole('button', { name: /increase/i }))
  expect(screen.getByText('Count: 1')).toBeInTheDocument()
})
Diagram 5: User-centered testingThe test follows the interface from accessible control to visible result, rather than reaching into internal component state.
UserButtonInteractionUI changesVisible assertion
Four stages showing a component render, cursor interaction, visible update and successful assertion
Test the public behavior of a component. Render it, interact through the same controls a user sees, and assert the observable result.
Diagram 6: Interaction test flowA component interaction test renders, finds an accessible element, performs the action, waits for UI change when needed, and asserts the result.
RenderFind elementUser interactionUI updateAssertion

Querying Elements Accessibly

Prefer queries that resemble how people and assistive technology understand the page. getByRole with an accessible name is usually the strongest choice, followed by form-oriented getByLabelText. getByPlaceholderText, getByText and getByDisplayValue are useful when they match the interface. Use getByTestId as a deliberate fallback when semantic access is unavailable or inappropriate; test IDs are not inherently bad.

screen.getByRole('button', { name: /submit/i })
screen.getByLabelText(/email address/i)
screen.getByText(/saved successfully/i)

Role queries frequently reveal missing labels, wrong roles and absent accessible names. That improves test quality and can expose accessibility problems, but React Testing Library is not a complete accessibility audit. Automated accessibility checks, keyboard review, screen-reader evaluation and human judgment still matter.

Diagram 7: Accessible markup strengthens testsSemantic HTML creates roles and accessible names that Testing Library can query in a user-centered way.
Accessible markupSemantic rolesTesting Library queriesMore user-centered tests

Testing Forms, Validation and Async UI

Blog #18 covers forms and validation. Component tests should verify labels, typing, validation messages, submit behavior, and visible loading, error and success states. Use a mock callback or controlled request boundary—not the site’s real production API.

Diagram 8: Form behavior testA test enters input and submits. Valid data reaches the mocked success boundary; invalid data produces accessible error UI.
Render formEnter inputSubmit
Valid → successInvalid → error UI

Use findBy... when one element should appear asynchronously. Use waitFor when an assertion needs retrying, such as a mock being called after an asynchronous state transition. Avoid arbitrary sleep timers. A useful loading test verifies that the indicator appears after the request starts and disappears when the result becomes visible. Error tests should assert the message a user receives rather than only checking console output.

expect(await screen.findByText(/loaded/i)).toBeInTheDocument()

await waitFor(() => {
  expect(onSubmit).toHaveBeenCalledWith({ email: 'dev@example.com' })
})

Testing Client and Server Components

Client Components containing state, click handlers, forms, conditional rendering or browser interactions are natural component-test subjects. Synchronous Server Components may also be unit tested under current Next.js guidance when their dependencies fit the environment.

Diagram 9: Server Component decisionA synchronous Server Component may fit a Vitest unit test. For an async Server Component, current Next.js guidance recommends E2E coverage.
Server ComponentIs it async?
NoVitest may be appropriate
YesPrefer E2E testing
Choose by behaviorDo not disguise unsupported rendering with a fake workaround

Testing Server Actions and Business Logic

Blog #7 explains Server Actions. An action may combine validation, authentication, authorization, a database mutation, redirects and revalidation. Extract genuinely reusable pure rules when useful, test integration boundaries where they provide confidence, and preserve an E2E flow for the full behavior. Do not expose server internals merely to make tests easier.

export function calculateTotal(price: number, quantity: number) {
  return price * quantity
}
Diagram 10: Server Action test strategySeparate stable business rules from infrastructure boundaries, while keeping the complete user journey covered at the browser level.
Server Action
Validation helper → unitBusiness logic → unit/integrationFull user flow → E2E

Mocking Functions, Modules and fetch

Vitest provides vi.fn() for mock functions, vi.spyOn() for observing an object method, and vi.mock() for module boundaries. Use mocks to control dependencies that would otherwise be slow, nondeterministic or unsafe. Do not mock every collaborator: a suite where every contract is simulated can pass while the real integration is broken.

const onSubmit = vi.fn()
// interact with the form
expect(onSubmit).toHaveBeenCalled()

afterEach(() => {
  vi.unstubAllGlobals()
  vi.restoreAllMocks()
})
Diagram 11: A focused mock boundaryThe subject calls a dependency. A mock substitutes that boundary only when controlled behavior improves the focused test.
Component or functionDependency
Real integrationMock → controlled behavior

For a direct fetch boundary, vi.stubGlobal('fetch', vi.fn()) can represent a successful response, an HTTP error or a rejected network request. Remember that fetch normally resolves for HTTP 500 responses; application code must inspect response.ok. Restore global stubs after each test so state cannot leak into the next case.

Diagram 12: Mocked fetch outcomesOne controlled fetch boundary should cover a usable success response, an HTTP failure and a network rejection.
Componentfetch()
200 success500 responseNetwork error

Organizing Independent Tests

Each test should create the state it needs and clean up its own mock or timer changes. Test B must not depend on Test A running first. Small beforeEach or afterEach hooks can remove repetition, but enormous shared setup hides behavior and makes failures hard to understand.

Diagram 13: Test isolationEvery case owns its setup, execution and cleanup, so order and parallel scheduling cannot change the result.
Test A: setuptestcleanup
Test B: setuptestcleanup
Test C: setuptestcleanup

Test Coverage

Current Vitest supports V8 and Istanbul coverage providers. If the project intentionally selects V8, install the compatible @vitest/coverage-v8 package and add a test:coverage script such as vitest run --coverage. Pin compatible versions through the application’s lockfile rather than copying a version number from an article.

Diagram 14: What coverage reportsCoverage observes which source paths execute and summarizes statements, branches, functions and lines.
Source codeTests run
StatementsBranchesFunctionsLines

Testing in CI/CD

Blog #30 covers dependency and CI supply-chain controls. In an actual Next.js repository, run the locked install, lint, typecheck, focused tests and production build before deployment. Inspect existing workflows and scripts first, use the real package manager, avoid duplicate steps, and do not expose production secrets to unit-test jobs unnecessarily.

A source code change passing through six automated quality gates before a protected deployment
CI turns local checks into repeatable release gates. A change should cross the project’s real install, lint, type, test and build boundaries before deployment.
Diagram 15: CI testing pipelineA pushed change enters CI, installs from the lockfile, runs quality checks, builds the production artifact, and only then becomes eligible for deployment.
Git push
Locked installLint + typecheckUnit testsProduction build
Deploy

Production Testing Strategy

Prioritize business rules, user-visible behavior, validation, important branches, loading and error states, security-sensitive logic, expensive regressions and critical utilities. Avoid spending disproportionate time on framework internals, trivial static markup, internal React state and third-party library behavior. Snapshots can detect broad rendering changes, but behavioral assertions are usually clearer for interactive components.

Diagram 16: Complete testing strategyFast focused checks, meaningful integrations and a smaller set of critical browser journeys combine with CI and production observation.
Unit rulesComponent behavior
+
Integration boundariesE2E journeys
CI quality gatesProduction monitoring

A Practical Next.js Test Implementation

In a real Next.js application, begin with discovery rather than installation. Read the root package.json, its lockfile, tsconfig.json, next.config.*, application directories and CI workflows. Search for Vitest, Jest, Playwright, Cypress, Testing Library and existing test patterns. An established suite may already solve aliases, providers, browser APIs and cleanup. Replacing it merely because a tutorial uses another tool creates migration risk without improving confidence.

Confirm the exact Next.js and React versions from the installed manifest and lockfile. Check engines, .nvmrc, .node-version, Volta or CI configuration for a Node requirement. Detect npm, pnpm, Yarn or Bun from the committed lockfile and package-manager declaration, then use only that manager. Version compatibility belongs to the application’s resolved dependency graph, not the version visible on a documentation website today.

If no test framework exists, create the smallest vertical slice. Configure Vitest and jsdom, add one setup file only when shared matchers or cleanup require it, test a pure helper, and test one interactive Client Component. Run both locally before changing CI. This proves imports, JSX transformation, aliases, DOM environment and test discovery with a reviewable change.

Project areaWhat to inspectWhy it matters
Manifest + lockfileVersions, scripts, package managerAvoid duplicate or incompatible tools
TypeScriptAliases, JSX, included pathsKeep test imports aligned with the app
ComponentsClient boundaries, providers, browser APIsSelect realistic component candidates
Server codeAsync components, actions, route handlersChoose unit, integration or E2E correctly
CIInstall and quality-gate commandsDo not duplicate or weaken the pipeline

Suggested File Structure

Colocation keeps a test close to the source it protects. A shared test directory can make cross-feature integrations easier to scan. Both are valid. Follow the existing convention and use one naming pattern, normally .test.ts for non-JSX logic and .test.tsx for React elements.

src/
├── app/
├── components/
│   ├── Counter.tsx
│   └── Counter.test.tsx
├── lib/
│   ├── format-price.ts
│   └── format-price.test.ts
└── test/
    └── setup.ts

vitest.config.mts

Avoid a global setup file that imports the whole application or silently creates a large provider graph. Shared setup should establish stable testing infrastructure, such as DOM matchers. Feature-specific data and mocks belong near the test that depends on them.

A Welcome Component Test

A small prop-driven component demonstrates semantic queries without interaction. The heading role and accessible name describe the output a user receives. With jest-dom configured for Vitest, toBeInTheDocument() provides a readable assertion; without that package, the current Next.js example uses a standard Vitest assertion such as toBeDefined().

type WelcomeProps = { name: string }

export function Welcome({ name }: WelcomeProps) {
  return <h1>Welcome, {name}</h1>
}

it('greets the named visitor', () => {
  render(<Welcome name="Navdeep" />)
  expect(screen.getByRole('heading', {
    level: 1,
    name: /welcome, navdeep/i,
  })).toBeInTheDocument()
})

This test can survive a change from string concatenation to nested spans because the accessible heading remains the contract. A selector tied to a CSS class or DOM nesting would fail during a harmless presentation refactor.

A Focused Newsletter Form Test

Imagine a Client Component that accepts an onSubmit callback. It labels its email control, validates that a value resembles an email address, disables submission while awaiting the callback, shows an alert on failure and displays a status message after success. The component test does not call a real endpoint; the callback represents that external boundary.

it('submits a valid email and shows success', async () => {
  const user = userEvent.setup()
  const onSubmit = vi.fn().mockResolvedValue(undefined)
  render(<NewsletterForm onSubmit={onSubmit} />)

  await user.type(
    screen.getByLabelText(/email address/i),
    'dev@example.com',
  )
  await user.click(screen.getByRole('button', { name: /subscribe/i }))

  expect(onSubmit).toHaveBeenCalledWith('dev@example.com')
  expect(await screen.findByText(/subscription confirmed/i))
    .toBeInTheDocument()
})

Add a separate invalid-input case that confirms the callback is not called and the validation message is associated with the field. Add a rejected-callback case that asserts a user-visible alert and a retry path. Keeping these cases separate makes the failing contract clear.

Loading, Errors and Conditional UI

Asynchronous UI is a sequence, not merely a final assertion. A request-driven component usually begins idle, enters a loading state, then produces data or an error. A strong test controls the promise so it can observe the middle state. If the promise resolves immediately, the loading indicator may appear and disappear before the assertion can establish useful evidence.

Use findByRole or findByText when waiting for one element. These queries retry until the element appears or the configured timeout expires. Use waitFor when waiting for a broader assertion, such as a spy call or a set of conditions. Do not place side effects inside waitFor; retries could repeat them.

Error handling should describe what the user can do next. An alert may explain that data could not load and offer a retry button. Test the alert’s accessible content, activate retry, and return a successful controlled response. A test that only expects console.error misses the product behavior.

Conditional rendering includes permissions, feature flags, empty data, optimistic states and responsive alternatives. Test branches that change capability or meaning. Trivial decorative branches rarely deserve a dedicated test unless they have caused regressions or carry accessibility consequences.

Client Components, Browser APIs and Providers

The 'use client' directive marks a client boundary; it does not require every descendant to have an isolated test. Select components with meaningful state or behavior. A static wrapper may be sufficiently covered by a broader component or browser flow.

Context providers, data clients and theme systems can be supplied through a small custom render helper when many tests need the same stable wrapper. Keep the helper transparent: callers should be able to override initial state and understand which providers exist. Do not use a giant application render helper for pure components that need no context.

jsdom implements DOM behavior but does not perform browser layout or paint. Measurements such as getBoundingClientRect(), observer timing, focus edge cases, media queries and navigation may differ from Chromium, Firefox or WebKit. Mock a missing API only when the focused component test needs a controlled contract, and cover layout- or engine-sensitive behavior in E2E tests.

React development behavior can expose unsafe effects or duplicate assumptions. Assertions should wait for user-visible stability instead of relying on an exact incidental render count. If one call count is part of a contractual side effect, design the boundary so the test observes the intended action rather than React’s internal scheduling.

Server Logic, Route Handlers and Actions

Server code often crosses environment boundaries: request objects, cookies, headers, authentication, databases, caches, queues and revalidation. Classify the behavior before choosing a test. A deterministic parser or authorization predicate may be a unit. A Route Handler with a mocked service can be an integration. A session cookie and redirect across a deployed app usually belong in E2E coverage.

Server Actions should remain secure entry points. Unit tests for extracted validation or business logic do not prove authentication, authorization, origin checks, mutation behavior and revalidation all work together. Keep at least one broader test for high-risk mutations. Never weaken access controls or export private internals solely to reach them from a unit test.

For async Server Components, respect the current tooling boundary. Rendering an awaited function manually may test a helper or returned element in a narrow situation, but it should not be presented as official Vitest support for the component lifecycle. Current Next.js documentation recommends E2E testing, which can verify streaming, navigation, server data and hydration in the running application.

Keep the Test Suite Fast and Trustworthy

Speed affects whether developers run tests before pushing. Keep unit tests free from real network calls, production services, unnecessary databases and arbitrary timers. Use focused test data builders instead of enormous fixtures. Move a behavior to integration infrastructure only when the real dependency contributes meaningful confidence.

Use fake timers carefully. They are valuable for debouncing, scheduled retries and time-dependent rules, but user-event requires correct timer advancement configuration. Prefer testing the result of time passing rather than every scheduled callback. Always restore real timers after the case.

When a test fails intermittently, identify shared globals, unresolved promises, time assumptions, random data, port conflicts and order dependencies. Repeated retries can collect evidence temporarily, but they are not a permanent fix. A consistently green suite is more valuable than a larger suite developers do not trust.

What to Test—and What Not to Over-Test

Prioritize

  • Business and pricing rules
  • Validation and authorization decisions
  • User-visible component behavior
  • Loading, empty, success and error states
  • Critical transformations and utilities
  • Expensive past regressions
  • High-value user journeys

Avoid over-testing

  • React or Next.js implementation details
  • Internal state with no visible contract
  • Third-party library internals
  • Trivial static markup
  • Every private helper call
  • Snapshots as the only interaction evidence
  • Impossible TypeScript states without a runtime boundary

Snapshot testing can be useful for stable serialized output or a compact structure where any broad change deserves review. Large component snapshots often become noisy and are updated without understanding. Prefer a few direct behavioral assertions for interactive UI, and use visual regression tools when appearance itself is the contract.

A balanced suite is intentionally uneven. A calculation-heavy domain may have many unit tests. A content site may invest more in navigation, accessibility and browser rendering. A payment or authentication flow needs layered security and integration evidence. Copying another project’s test ratio or coverage threshold without its risk context rarely helps.

Common Next.js Testing Mistakes

  • Testing implementation details instead of visible behavior.
  • Inspecting internal React state directly.
  • Using only getByTestId when semantic queries fit.
  • Mocking every dependency and creating false confidence.
  • Failing to restore spies, globals, timers or modules.
  • Making tests depend on execution order.
  • Calling production APIs from unit tests.
  • Assuming Vitest supports async Server Components.
  • Treating unit tests as a replacement for E2E coverage.
  • Treating broad E2E tests as a replacement for fast unit tests.
  • Chasing 100% coverage instead of important risk.
  • Testing React or Next.js internals.
  • Building huge test files with hidden setup.
  • Using fragile selectors unrelated to user behavior.
  • Ignoring error and loading states.
  • Keeping tests local and forgetting CI.
  • Rendering without a meaningful assertion.

Next.js 16 Testing Best Practices

  • Test behavior rather than implementation.
  • Start with important business logic.
  • Use accessible queries and prefer getByRole when appropriate.
  • Use user-event for realistic interactions.
  • Keep tests independent and readable.
  • Restore mocks and global stubs.
  • Avoid unnecessary mocking.
  • Test success, failure, validation and loading.
  • Keep pure business logic easy to call.
  • Use E2E for async Server Components.
  • Use E2E for critical user journeys.
  • Run deterministic tests in CI.
  • Treat coverage as a signal, not a target.
  • Follow current official Next.js, Vitest and Testing Library guidance.

Testing & Quality Roadmap

#31 Vitest & React Testing LibraryPublished

#32 Playwright E2E TestingPublished

#33 Server/Client Components & Server Actions TestingPlanned

#34 Route Handlers & API TestingPlanned

#35 Authentication TestingPlanned

#36 Database TestingPlanned

#37 Mocking APIs & External ServicesPlanned

#38 Playwright Authentication & User FlowsPlanned

#39 Testing with GitHub ActionsPlanned

#40 Production Testing StrategyPlanned

Frequently Asked Questions

What is Vitest?

Vitest is a Vite-powered test framework for JavaScript and TypeScript. It provides a test runner, assertions, mocking, watch mode and optional coverage.

Can I use Vitest with Next.js 16?

Yes. The current Next.js guide documents Vitest for unit tests and synchronous Server and Client Components. Async Server Components should be covered with end-to-end tests.

What is React Testing Library?

React Testing Library renders React components and provides queries that encourage tests based on user-visible output and accessible behavior.

What is the difference between Vitest and React Testing Library?

Vitest runs tests and supplies assertions and mocks. React Testing Library renders React UI and helps tests find and interact with its DOM output.

What is unit testing?

Unit testing verifies a small isolated piece of logic, such as a formatter, calculation or validation helper.

What is component testing?

Component testing verifies React output, props and user interactions in a controlled DOM-like environment.

What is integration testing?

Integration testing checks multiple pieces working together, such as a form, validation helper and mocked submission boundary.

What is E2E testing?

End-to-end testing drives a real browser through a complete user workflow against a running application.

Should I use Vitest or Playwright?

Use Vitest for focused logic and component feedback. Use Playwright for real browser navigation, async Server Components and critical user journeys. Most production applications benefit from both.

Can Vitest test Client Components?

Yes. Client Components with state, event handlers, forms and conditional UI are natural component-test candidates.

Can Vitest test Server Components?

Synchronous Server Components can be unit tested according to current Next.js guidance, subject to their dependencies and environment.

Can Vitest test async Server Components?

Current Next.js documentation says Vitest does not support async Server Components. Prefer end-to-end testing for those components.

How do I test a button click?

Render the component, find the button by role and accessible name, click it with a user-event instance, then assert the visible result.

How do I test a form?

Query labeled controls, enter realistic values, submit through the visible control, and verify validation, loading, error and success states.

How do I mock fetch with Vitest?

Stub the global fetch function with vi.stubGlobal or mock a small request wrapper. Cover success, HTTP failure and network rejection, then restore the stub after each test.

Should I mock every API?

No. Mock narrow boundaries in focused tests, then retain integration and E2E coverage so real contracts are still verified.

What is jsdom?

jsdom implements many browser DOM APIs in Node so component tests can render and query HTML. It is not a complete real browser.

What is test coverage?

Coverage reports which statements, branches, functions and lines executed during tests. It does not prove that assertions are meaningful.

Do I need 100% test coverage?

No. Prioritize risky business rules, important user behavior, validation, failures and expensive regressions instead of chasing an arbitrary percentage.

Should tests run in GitHub Actions?

Yes when GitHub Actions is the project CI. Use the real package manager and run installation, lint, types, tests and build without exposing unnecessary secrets.

Current Official References

Next Steps

Apply this guide to the real Next.js application by auditing its package manager, aliases, components and existing test architecture first. Begin with one high-value pure rule and one user-visible Client Component behavior, then add failure cases and CI execution. Use Blog #10 for Route Handler context, Blog #13 for authentication boundaries, and Blog #24 for production feedback. Continue with Blog #32 for Playwright E2E testing.

WhatsApp