Testing is crucial for building reliable Next.js applications. This comprehensive guide will walk you through setting up and writing tests using Jest and React Testing Library, building upon your notes with additional important concepts and best practices.
Table of Contents
Understanding the Testing Next.js Stack
Key Testing Tools and Their Purposes
- Jest: The test runner that executes your tests and provides the testing framework (describe, it, expect).
- React Testing Library: Provides utilities to test React components in a way that resembles how users interact with them.
- @testing-library/jest-dom: Adds custom Jest matchers for more readable assertions about DOM elements.
- @testing-library/user-event: Simulates user interactions more realistically than basic events.
Step 1: Install Dependencies for testing next.js
Before creating and running the test, we need to add require testing packages and need to setup the configuration, let first do the installation. You’ll need to add jest, jest-environment-jsdom, @testing-library/react, and @testing-library/jest-dom to your project’s development dependencies.
npm install -D jest jest-environment-jsdom @testing-library/react @testing-library/jest-dom @testing-library/user-event
We also need @testing-library/user-event which is important for simulating user interactions more realistically than the basic fireEvent.
For typescript, Even if Next.js auto-configures some of this, it’s best to install explicit Jest types for TS projects.
npm install -D @types/jest
In tsconifg.json file we need to configure this file
....
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "jest.setup.js"],
Step 2: Configure Jest
This file is used to configure Jest for your Next.js project. It specifies various settings such as the test environment, module name mapping, setup files, and test path ignore patterns. In your code, you can see that jest.config.js is using nextJest to create a Jest configuration object with the specified settings. The configuration object is then exported as module.exports.
We need to create a jest.config.js file in the root of your project. The next/jest utility will automatically handle Next.js-specific configurations, such as transforming code with SWC and mocking image/CSS imports.
Here, we had two version of the jest configuration, the first one jest.config.js file is
Simple jest.config.js (Minimum Required).
Here is the simple config is perfectly fine for getting started! It handles:
- ✅ Basic Next.js setup
- ✅ TypeScript/JavaScript files
- ✅ Path aliases (@/ imports)
- ✅ Excluding build folders
// jest.config.js
const nextJest = require("next/jest");
const createJestConfig = nextJest({
dir: "./", // Path to your Next.js app directory
});
/** @type {import('jest').Config} */
const customJestConfig = {
setupFilesAfterEnv: ["<rootDir>/jest.setup.js"],
testEnvironment: "jest-environment-jsdom",
moduleNameMapper: {
// Handle module aliases (same as in tsconfig.json)
"^@/(.*)$": "<rootDir>/$1",
},
testPathIgnorePatterns: ["<rootDir>/.next/", "<rootDir>/node_modules/"],
};
module.exports = createJestConfig(customJestConfig)
Comprehensive jest.config.js (With Optional Enhancements)
The extended version I showed adds optional features that you might need later:
// jest.config.js
const nextJest = require("next/jest");
const createJestConfig = nextJest({
dir: "./", // Path to your Next.js app directory
});
/** @type {import('jest').Config} */
const customJestConfig = {
setupFilesAfterEnv: ["<rootDir>/jest.setup.js"],
testEnvironment: "jest-environment-jsdom",
moduleNameMapper: {
// Handle module aliases (same as in tsconfig.json)
"^@/(.*)$": "<rootDir>/$1",
// Handle CSS imports (with CSS modules)
"^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy",
// Handle CSS imports (without CSS modules)
"^.+\\.(css|sass|scss)$": "<rootDir>/__mocks__/styleMock.js",
// Handle image imports
"^.+\\.(jpg|jpeg|png|gif|webp|avif|svg)$": "<rootDir>/__mocks__/fileMock.js",
},
testPathIgnorePatterns: ["<rootDir>/.next/", "<rootDir>/node_modules/"],
collectCoverageFrom: [
"app/**/*.{js,jsx,ts,tsx}",
"components/**/*.{js,jsx,ts,tsx}",
"!**/*.d.ts",
"!**/node_modules/**",
"!**/.next/**",
],
coverageThreshold: {
global: {
branches: 70,
functions: 70,
lines: 70,
statements: 70,
},
},
};
module.exports = createJestConfig(customJestConfig);
Your simple jest.config.js is perfect for starting! The comprehensive version had includes features we might never need:
- CSS/SCSS handling → Only if you import styles in components
- Image mocks → Only if you import images directly
- Coverage settings → Only if your team requires coverage reports
- Coverage thresholds → Only if you want to enforce minimum coverage
My Recommendation
- Start with your simple config ✅
- Write your tests
- Only add extra config when you hit specific errors
For example, if you never import CSS files in your components (maybe you use Tailwind classes only), you’ll never need the CSS configuration. If you only use Next.js Image component with URLs (not imported images), you won’t need image mocks.
The beauty of Next.js with Jest is that the simple config handles most scenarios out of the box. Add complexity only when you need it!
Step 3: Create jest.setup.js
Let create a file called jest.setup.js in root folder – Setup file for additional test utilities (like @testing-library/jest-dom), is used to set up the environment before running the tests. For starting we can only have only one line of code in this file import ‘@testing-library/jest-dom’; other are optional.
// jest.setup.js
import '@testing-library/jest-dom';
// Optional: Add custom global test utilities
global.matchMedia = global.matchMedia || function() {
return {
matches: false,
addListener: function() {},
removeListener: function() {}
};
};
// Mock IntersectionObserver
global.IntersectionObserver = class IntersectionObserver {
constructor() {}
disconnect() {}
observe() {}
unobserve() {}
takeRecords() {
return [];
}
};
Step 4: Update package.json
For starting we can add only two test script test and test:watch. Add a Test Script: Modify your package.json to include a test script that runs Jest.
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:ci": "jest --ci --coverage --maxWorkers=2"
}
}
Step 5: Project Structure
Write Your First Test: Create a __tests__ directory and write a simple test for a component or page to verify that your setup is working correctly. We dont need ts-jest, is already with nextjs latest.
We can either create test in individual files or folder, best approach it to create separete folder for testing in root folder as
my-next-app/
├─ app/
│ ├─ page.tsx
│ └─ layout.tsx
├─ components/
│ └─ Button.tsx
├─ __tests__/ ← (optional, or you can place tests next to files)
│ └─ Button.test.tsx
├─ jest.config.js ← ✅ Add this here
├─ jest.setup.js ← optional setup file
├─ next.config.js
├─ package.json
├─ tsconfig.json
└─ README.md
Once followed all the step from 1 to 5, then our Next.js Jest setup is complete:
✅ Dependencies installed
✅ jest.config.js created✅ jest.setup.js created
✅ Package.json scripts added
You’re ready to write and run tests. Next step: Create your first test file (e.g., tests/example.test.js or
components/MyComponent.test.js).
Step 6: Creating first test case for Home or index page
In our root folder __tests__, let create file called pages/Home.test.tsx and following test case
import { render, screen, within, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom";
import Home from "@/app/page";
// 1. BASIC RENDERING TESTS
describe("Home Page - Basic Rendering", () => {
beforeEach(() => {
render(<Home />);
});
it("renders without crashing", () => {
expect(screen.getByRole("main")).toBeInTheDocument();
});
it("displays brand name and main heading", () => {
expect(screen.getByText("SME")).toBeInTheDocument();
expect(screen.getByText(/Unlock Your Growth/i)).toBeInTheDocument();
expect(screen.getByText(/Potential/i)).toBeInTheDocument();
});
it("has proper semantic structure", () => {
expect(screen.getByRole("banner")).toBeInTheDocument(); // header
expect(screen.getByRole("main")).toBeInTheDocument();
expect(screen.getByRole("contentinfo")).toBeInTheDocument(); // footer
});
});
The Home.test.tsx file is a test file written using the Jest testing framework. It contains test cases for the Home component, which is a page component in your application.
- The
describefunction is used to group related test cases together. In this case, the test cases are grouped under the description “Home Page – Basic Rendering”. - The
beforeEachfunction is used to set up the test environment before each test case. In this case, it renders theHomecomponent using therenderfunction from the@testing-library/reactlibrary. - The test cases themselves are defined using the
itfunction. Each test case has a description and a callback function that contains the assertions. - The assertions are made using the
expectfunction from the@testing-library/jest-domlibrary. These assertions check if certain elements are present in the rendered output of theHomecomponent. - In this specific test file, the test cases check if the
Homecomponent renders without crashing, displays the brand name and main heading correctly, and has the proper semantic structure.
Overall, this test file ensures that the Home component behaves as expected and provides a comprehensive set of test cases to ensure its functionality and user experience.
Testing Best Practices
1. Write Tests That Resemble User Behavior
Focus on testing what users see and do, not implementation details. Use semantic queries (getByRole, getByLabelText) over test IDs.
2. Follow the AAA Pattern
- Arrange: Set up test conditions
- Act: Perform the action being tested
- Assert: Check the expected outcome
3. Use Descriptive Test Names
Write test descriptions that explain what is being tested and what the expected behavior is.
4. Keep Tests Independent
Each test should be able to run in isolation. Use beforeEach/afterEach for setup and cleanup.
5. Mock External Dependencies
Mock API calls, timers, and external services to make tests predictable and fast.
6. Test Coverage Guidelines
- Aim for 70-80% coverage as a baseline
- Focus on critical user paths
- Don’t chase 100% coverage at the expense of test quality