Code coverage standards for a Next.js project using CircleCI and Coveralls

Front-end Developer

An essential part of software development, testing helps catch bugs and errors early, improves software quality, and ultimately prevents costly issues from being deployed to production. The effectiveness of software testing will remain uncertain until it can be measured and that is where code coverage comes in.
Code coverage is a metric that tells developers what portion of their codebase is executed when specific tests are run. It accurately measures how much of your code is actually being tested, exposing areas of code that may require additional testing.
In this tutorial, you will learn the importance of code coverage and how to enforce it in a Next.js project. You will also learn how to automate test coverage reports using CircleCI, a perfect Continous Integration and Deployment (CI/CD) tool.
Prerequesites
To follow along with this tutorial, you will need the following:
- A GitHub account.
- A CircleCI account.
- A Coveralls account.
- Node.js version 14 or higher.
Cloning a Next.js project
To get started, you will need a project with some basic interaction - such as a click functionality. While this tutorial makes use of a Todo list app built with Next.js, you can follow through with any similar project of your choice.
To clone the sample project, enter this command in your terminal:
git clone https://github.com/CIRCLECI-GWP/Todo-list-app
cd Todo-list-app
git checkout starter
Note: This project is a basic Todo list app that allows users to add, modify, and delete list items.
Next, ensure your project is version-controlled with Git and pushed to a GitHub repository. To do this, follow the steps outlined in the linked blog post, as it serves as a great resource for pushing projects to GitHub.
Note: If you are using the free version of Coveralls, your GitHub repository must be public. To use Coveralls with a private repository, you will need a paid subscription.
Setting up Jest for testing and coverage
With your project now version-controlled and pushed to GitHub, it is time to enforce code coverage standards on it. However, before you can measure coverage or enforce coverage thresholds, you will need to set up a solid testing environment.
This tutorial focuses on writing interaction tests for your Todo list app. To achieve this, you will utilize Jest as your test runner and React Testing library to simulate user interactions.
To get started, run this command in your terminal:
npm install --save-dev jest jest-environment-jsdom
The above command installs Jest along with jest-environment-jsdom
, a simulated DOM environment used for testing browser-based components.
Next, install React testing library along with its peerDependency by running this command in your terminal:
npm install --save-dev @testing-library/react @testing-library/dom @testing-library/user-event
Generate a Jest configuration file by entering this command:
npm init jest@latest
This will prompt you to answer a series of questions:
Would you like to use Jest when running "test" script in "package.json"?
Choose yes.Would you like to use Typescript for the configuration file?
Select No (or choose Yes if you want to use typescript).Choose the test environment that will be used for testing
: Select jsdom (browser-like).Do you want Jest to add coverage reports?
Choose yes.Which provider should be used to instrument code for coverage?
Select v8.Automatically clear mock calls, instances, contexts and results before every test?
Select yes.
Once correctly answered, a basic jest.config.js
file will be created at the root of your project.
Finally, replace the contents of your jest.config.js
file with the next/jest
transformer configuration, which provides all the necessary options for Jest to work seamlessly with Next.js:
const nextJest = require('next/jest')
/** @type {import('jest').Config} */
const createJestConfig = nextJest({
dir: './',
})
const config = {
coverageProvider: 'v8',
testEnvironment: 'jsdom',
}
module.exports = createJestConfig(config)
In the configuration above:
const nextJest = require('next/jest')
imports thenext/jest
package, a wrapper that ensures Jest runs seamlessly with Next.js.const createJestConfig = nextJest({dir:'./',})
provides thenextJest
function with the root directory of your Todo list app.const config = {}
holds your custom Jest settings.module.exports = createJestConfig(config)
exports your Jest configuration, making it available to Jest when your tests run.
With Jest and React testing library now installed and Jest properly configured, you can start to write interaction tests for your Todo List app.
Go to your project directory and create a tests
folder. within this folder and create a List.test.js
file. Update its content with this:
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import List from '../src/app/component/List';
describe('Todo List App', () => {
it('adds a new item to the list', async () => {
render(<List />);
const input = screen.getByPlaceholderText(/add item/i);
const button = screen.getByRole('button', { name: /add/i });
await userEvent.type(input, 'Write tests');
await userEvent.click(button);
expect(screen.getByText('Write tests')).toBeTruthy();;
});
it('should not add an empty item', async () => {
render(<List />);
const input = screen.getByPlaceholderText(/add item/i);
const button = screen.getByRole('button', { name: /add/i });
const initialItems = screen.queryAllByRole('listitem').length;
await userEvent.type(input, ' ');
await userEvent.click(button);
const updatedItems = screen.queryAllByRole('listitem').length;
expect(updatedItems).toBe(initialItems);
});
});
In the above configuration:
render(<List />);
renders theList
component defined insrc/app/component/List.jsx
of your Todo List app to the virtual DOM.- Both
const input = screen.getByPlaceholderText(/add item/i);
andconst button = screen.getByRole('button', { name: /add/i });
select the input and button elements in your List component, mimicking how a user would interact with them.
In the first test:
await userEvent.type(input, 'Write tests');
simulates typing a new todo list item whileawait userEvent.click(button);
simulates the clicking of the “Add” button.expect(screen.getByText('Write tests')).toBeTruthy();
verifies that entering a valid text adds a new item to your todo list.
In the second test:
const initialItems = screen.queryAllByRole('listitem').length;
checks the number of list items present in the app before the user types whitespaces and attempts to submit an empty list.await userEvent.type(input, ' ');
simulates entering whitespaces whileawait userEvent.click(button);
attempts to add an empty item to your todo list.const updatedItems = screen.queryAllByRole('listitem').length;
gets the number of list items after the submission attempt to compare with the initial count.expect(updatedItems).toBe(initialItems);
verifies that the number of list items remains the same, ensuring that an empty list item is not added to the app.
Go to your package.json
file and add this under the scripts
section:
"test:coverage": "jest --coverage"
This script instructs Jest to generate a code coverage report when the tests are run.
Finally, run Jest with coverage by entering the below command in your terminal:
npm run test:coverage
There you have it, Jest now run tests on your Todo List app, generating a detailed coverage report. This report reveals the percentage of statements, branches, functions and lines that are covered by your Jest test, as well as uncovered code areas.
Integrating Coveralls for code coverage reporting
With Jest now running tests and generating code coverage reports, you can take things a step further by integrating a tool to track and analyze the code coverage metrics of your Todo List app. An option for this is Coveralls.
Coveralls enable developers to check the percentage of their code tested, while allowing the monitoring of coverage trends over time.
To begin integrating Coveralls with your Next.js project, start by logging in to your Coveralls account.
Next, go to the Add Repos section in your Coveralls dashboard, and search for your project repo. For this tutorial it’s todo-list-app
, and toggle it on to add it to Coveralls.
Head over to the Repos section where you will find your newly added Repository and click on the Start Uploading Coverage button to continue your setup.
Click the Start Uploading Coverage button. A Coveralls repo token will be generated for you. Scroll down the page to find the generated token. Make sure you securely store this token; you will be needing it later on in the tutorial to view coverage reports in Coveralls.
Note: To continue with uploading your coverage reports to Coveralls, you will have to integrate your project with a CI/CD tool. This tutorial makes use of CircleCI, a powerful CI/CD tool for this purpose.
Automating testing and coverage reporting with CircleCI
Moving forward, you can automate your workflow so that Jest tests are carried out, coverage reports are generated and those reports uploaded to Coveralls automatically whenever changes are made and pushed to GitHub. To achieve this, you will require a CI/CD tool, of which CircleCI is a perfect fit.
Note: CircleCI offers a Coveralls orb that makes integrating Coveralls into your pipeline easy. This tutorial will utilize that orb.
To begin your CircleCI integration, head over to your project directory and create a .circleci
folder at the root of this directory.
Next, within your .circleci
folder, create a config.yml
file and update it with these configurations.
version: 2.1
orbs:
coveralls: coveralls/coveralls@2.2.5
jobs:
test:
docker:
- image: cimg/node:22.15.0
steps:
- checkout
- run:
name: Install dependencies
command: npm ci
- run:
name: Run test with coverage
command: npm run test:coverage
- coveralls/upload
workflows:
test-and-coveralls:
jobs:
- test
The above CircleCI configuration utilizes version 2.2.5 of the Coveralls orb to simplify sending coverage reports to Coveralls. The entire job runs inside a Docker container, using the Node.js 22.15.0 image to ensure a consistent run time environment.
It proceeds to installs necessary dependencies using the npm ci
command, and runs Jest test with coverage using the npm run test:coverage
command.
The coveralls/upload
step then sends the generated coverage report to Coveralls, while the test-and-coveralls
workflow ties everything together, automating the entire process and triggering a pipeline build whenever changes are made to GitHub.
Finally, commit and push changes to GitHub.
Setting up the project on CircleCI
To set up your project, start by logging in to your CircleCI account and creating a project.
If you trigger your project’s pipeline at this point and you haven’t opted in to allow the usage of uncertified orbs for your organization, it will throw an error as CircleCI by default restricts uncertified orbs for security purposes.
To get your pipeline running correctly, you will need to enable permission for your Coveralls orb as well as add your Coveralls repo token as an environment variable in CircleCI. To do this, select Organization Settings from your CircleCI dashboard sidebar to go to the “Organization Settings” page.
On the the Organization Settings page, select Security from the sidebar. Check the Allow uncertified public orbs box.
You now need to provide the Coveralls token you obtained earlier. On your CircleCI dashboard, select Projects from the sidebar, open your project’s context menu, and select Project Settings.
On the “Settings” page, select Environment Variables from the sidebar and then click the Add Environment variable button. Create a new environment variable with the key COVERALLS_REPO_TOKEN
and enter the token you obtained from Coveralls as the value.
You can now trigger your pipeline again. With everything properly configured, your build should be successful.
To view your coverage report, head over to your Repo’s dashboard on the Coveralls website.
Note: This tutorial aims to demonstrate how to generate code coverage reports using Coveralls, which is why we are content with an 85% coverage result. However, for your future projects, prioritize writing meaningful tests that cover core functionality of your app. While a 100% coverage report can be a useful goal, know that it is not always necessary or practical.
Simulating coverage failure
For team leads aiming to enforce high test coverage in projects, you can configure a specific coverage threshold in your test setup. This ensures that builds fail automatically on CircleCI when changes introduced by team members cause coverage to fall below the defined minimum, helping maintain code quality and accountability across the team.
Note: Currently, the test coverage result of this tutorial’s project is 85%. To demonstrate what happens when coverage results do not meet the defined minimum, we will set the minimum coverage threshold to 100%.
Go to your jest.config.js
file and add this code to your const config = {}
block:
coverageThreshold: {
global: {
branches: 100,
functions: 100,
lines: 100,
statements: 100,
},
},
Commit and push these changes to GitHub. This will trigger a CircleCI pipeline build, which should fail because the total test coverage (85%) falls below the required coverage threshold (100%).
To ensure a successful build, you can write more tests to increase your project coverage to meet your set threshold or lower your coverageThreshold
values to meet your current coverage level.
Conclusion
So far, you have learned how to configure and write Jest tests, generate code coverage reports, integrate Coveralls to monitor those reports and automate the entire process using CircleCI. You also learned how to enforce code coverage thresholds to ensure team members write sufficient tests to maintain code quality.
Moving forward, take the opportunity to apply all you have learned to both new and existing projects. While 100% coverage isn’t always possible (or necessary), striving for high and meaningful coverage can make your codebase more reliable, easier to maintain, and better suited for team collaboration.
You can view the complete source code for this project on GitHub.