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
Post a Comment