Guide to Setting Up Jest Testing for a NextJS Project

Introduction

In this article, I will guide you on adding Jest (currently the most popular testing library) to test for projects using the NextJS framework. This integration helps developers automate the source code testing process, from logical functions (Unit Testing) to user interface testing (Integration Testing) in the Node.js environment.

Here are the prominent advantages:

  • Fast and effective: Jest runs tests in parallel, saving significant time as the project scales.
  • Built-in Support: Next.js provides the next/jest configuration, making setup extremely simple, automatically handling CSS files, images, and framework-specific features.
  • Excellent Watch Mode: Jest has the ability to detect recently changed files and only run related tests, keeping the development workflow smooth.
  • Coverage Reports: This tool has built-in capability to statistic the percentage of source code tested, helping you assess application quality and reliability visually.
  • Rich ecosystem: When combined with React Testing Library, you can test applications based on real user behavior instead of testing internal code structure details, making tests more resilient when the interface changes.

Detail

First, create the jest.config.ts file

import type {Config} from 'jest'
import nextJest from 'next/jest.js'

const createJestConfig = nextJest({
dir: './',
})

const config: Config = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
testEnvironment: 'jest-environment-jsdom',
moduleNameMapper: {
'^app/(.*)$': '<rootDir>/app/$1',
},

collectCoverage: true,
coverageProvider: 'v8',
collectCoverageFrom: [
'app/**/*.{js,jsx,ts,tsx}',
'!app/**/*.d.ts',
'!app/layout.tsx',
'!app/api/**',
'!**/node_modules/**',
],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html'],
}

export default createJestConfig(config)

This code snippet configures Jest to work with NextJS, setting up a browser simulation environment (jsdom), mapping directory paths, and setting up automatic coverage report generation for source code files in the app directory.


Next is the jest.setup.ts file

import '@testing-library/jest-dom'

Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
})

This file extends Jest's capabilities with matchers from testing-library and mocks the browser's matchMedia function so that UI components (like Ant Design) can run normally in the test environment.


Update the package.json file to add scripts for testing

{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}


Create the counter/page.tsx file for a simple counter function

'use client'

import {MinusOutlined, PlusOutlined, ReloadOutlined} from '@ant-design/icons'
import {Button, Card, Space, Typography} from 'antd'
import {useState} from 'react'

const {Title} = Typography

export default function Counter() {
const [count, setCount] = useState(0)

return (
<div className="flex justify-center items-center min-h-[200px]">
<Card className="shadow-lg w-full max-w-sm text-center">
<Title level={2} data-testid="count-value" className="mb-6">
{count}
</Title>

<Space size="middle">
<Button
type="primary"
danger
icon={<MinusOutlined />}
onClick={() => setCount(count - 1)}
aria-label="decrement"
>
Decrement
</Button>

<Button
icon={<ReloadOutlined />}
onClick={() => setCount(0)}
aria-label="reset"
>
Reset
</Button>

<Button
type="primary"
className="bg-green-600 hover:bg-green-500"
icon={<PlusOutlined />}
onClick={() => setCount(count + 1)}
aria-label="increment"
>
Increment
</Button>
</Space>
</Card>
</div>
)
}

This is a React component using Ant Design to create a Counter interface, allowing users to increase, decrease, or reset the value to 0 via buttons.


When starting the app, it will look like this


Create the counter.test.tsx file to test for the counter app

import {render, screen, fireEvent} from '@testing-library/react'
import Counter from 'app/counter/page'
import '@testing-library/jest-dom'

describe('Counter Component', () => {
test('should display the default value of 0', () => {
render(<Counter />)
const countValue = screen.getByTestId('count-value')
expect(countValue).toHaveTextContent('0')
})

test('should increment the value when the increment button is clicked', () => {
render(<Counter />)
const incrementBtn = screen.getByLabelText('increment')
const countValue = screen.getByTestId('count-value')

fireEvent.click(incrementBtn)
expect(countValue).toHaveTextContent('1')
})

test('should decrement the value when the decrement button is clicked', () => {
render(<Counter />)
const decrementBtn = screen.getByLabelText('decrement')
const countValue = screen.getByTestId('count-value')

fireEvent.click(decrementBtn)
expect(countValue).toHaveTextContent('-1')
})

test('should reset the value to 0 when the reset button is clicked', () => {
render(<Counter />)
const incrementBtn = screen.getByLabelText('increment')
const resetBtn = screen.getByLabelText('reset')
const countValue = screen.getByTestId('count-value')

fireEvent.click(incrementBtn)
fireEvent.click(incrementBtn)
fireEvent.click(resetBtn)

expect(countValue).toHaveTextContent('0')
})
})

This code snippet contains automated tests (unit tests) to check if the Counter component displays the correct default value and if the increment, decrement, and clear value functions operate correctly when the user clicks.


The result when running the test will be as follows:

$ yarn test
yarn run v1.22.22
$ jest --coverage
PASS test/counter.test.tsx
Counter Component
should display the default value of 0 (150 ms)
should increment the value when the increment button is clicked (77 ms)
should decrement the value when the decrement button is clicked (58 ms)
should reset the value to 0 when the reset button is clicked (92 ms)
-------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
All files | 20.73 | 57.14 | 57.14 | 20.73 |
app | 0 | 0 | 0 | 0 |
page.tsx | 0 | 0 | 0 | 0 | 1-6
app/counter | 100 | 100 | 100 | 100 |
page.tsx | 100 | 100 | 100 | 100 |
app/home | 0 | 0 | 0 | 0 |
page.tsx | 0 | 0 | 0 | 0 | 1-65
app/sso | 0 | 0 | 0 | 0 |
page.tsx | 0 | 0 | 0 | 0 | 1-124
-------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 4.788 s
Ran all test suites.
Done in 6.01s.


After running the test, it will create a coverage folder as follows


This is the test run result


Happy coding!

See more articles here.

Comments

Popular posts from this blog

All Practice Series

Deploying a NodeJS Server on Google Kubernetes Engine

Kubernetes Deployment for Zero Downtime

Setting up Kubernetes Dashboard with Kind

Using Kafka with Docker and NodeJS

Monitoring with cAdvisor, Prometheus and Grafana on Docker

Practicing with Google Cloud Platform - Google Kubernetes Engine to deploy nginx

Kubernetes Practice Series

NodeJS Practice Series

Sitemap