Cookies
This website uses cookies to improve the user experience, more information on the legal information path.
Notes
0
years
0
mons
0
days
0
hour
0
mins
0
secs
00
This is the background image
3

How to publish the test coverage report in your React application

You run jest --coverage, get a nice HTML report in coverage/, and then it dies on your machine. This post turns that report into a page anyone can open — useful for an open-source project where contributors want to see how well things are tested, or just to show your work. The stack is minimal:

A perfect "alternative" to SonarQube for small projects.

Publishing a test report is not something everyone does, and some consider it imprudent because you can leak internal detail. The honest use case is an open-source repo where the report helps people contribute, or a teaching post. If you want it gated, a path protected by user and password is enough.

PD: this example is a Next.js app, but it works for any web app that uses jest.

1. Path for the report

First, point the jest coverageDirectory at a public folder:

jest.config.js

const customJestConfig = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
coveragePathIgnorePatterns: ['^.*\\.stories\\.[jt]sx?$'],
coverageDirectory: 'public/coverage',
testEnvironment: 'jest-environment-jsdom',
coverageReporters: ['html'],
collectCoverageFrom: ['src/components/**/*.tsx'],
moduleNameMapper: {
'^@/helpers(.*)$': '<rootDir>src/helpers/index.ts$1',
'^@/layout(.*)$': '<rootDir>src/components/Layout/index.tsx$1',
'^@/test$': '<rootDir>/jest.setup.js',
'^@/components(.*)$': '<rootDir>src/components/$1',
'^@/context(.*)$': '<rootDir>src/context/$1',
'^@/hooks(.*)$': '<rootDir>src/hooks/$1',
'^@/constants(.*)$': '<rootDir>src/constants/$1',
},
};

collectCoverageFrom matters more than people think: without it, coverage is computed only over files a test happened to import, which flatters your numbers. Point it at the real source globs so untested files count as 0, not as absent.

2. Script to modify the report

The report references its CSS/JS/images with relative paths that break once it is served from a sub-path. This script rewrites those paths and customises a couple of texts:

If you prefer a class, use a Jest CustomReporter and implement onRunComplete.

custom-reporter.js

import { readFile, writeFile } from 'fs';
import glob from 'glob';
glob('public/coverage/**/*.?(html|css)', function (err, files) {
if (err) {
console.log('err', err);
return;
}
files.forEach((path) => {
readFile(path, 'utf8', (err, data) => {
if (err) {
console.log('err', err);
return;
}
let replaced = data.replace(
/Code coverage generated by\n\s*<a href="https:\/\/istanbul\.js\.org\/"/g,
`Code coverage automatically generated by Xabier Lameiro on ${new Date().toLocaleDateString()}`
);
if (path === 'public/coverage/index.html') {
replaced = replaced.replace(/src="/g, 'src="coverage/');
replaced = replaced.replace(/href="/g, 'href="coverage/');
}
writeFile(path, replaced, 'utf-8', function (err, a) {
if (err) {
console.log('err', err);
return;
}
});
});
});
});

3. Generate the report on build

Add a postbuild step so the report lands in the public folder every time you build:

package.json

{
"scripts": {
"dev": "next dev",
"build": "next build",
"postbuild": "npm run coverage",
"start": "next start",
"lint": "next lint",
"test": "NODE_ENV=test jest",
"coverage": "rm -rf public/coverage && NODE_ENV=test jest --coverage && node custom-reporter.js",
"watch": "jest --watchAll",
"test:clear": "jest --clearCache",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
}
}

4. Create a redirect on the host

So a clean URL serves the folder's index.html:

next.config.js

rewrites: async () => {
return [
{
source: '/:coverage',
destination: '/:coverage/index.html',
},
];
},

One thing I got wrong: keep it out of Google

Here is the part no tutorial warns you about, and it bit this very site. Once the coverage report was public, Google indexed every one of those generated HTML pages — dozens of near-identical, text-thin files. That is textbook low value content, and it dilutes the ranking signals of your real pages; it was part of what got this domain flagged by AdSense. Publishing the report is fine, but tell search engines to ignore it:

  • Inject <meta name="robots" content="noindex"> into the generated HTML (the same reporter script that fixes the paths can add it), or
  • Serve an X-Robots-Tag: noindex header for the report path or subdomain.

A coverage report is for humans who go looking for it, not for search results. Ship it, then hide it from crawlers.

Here is an example of what the report of this blog looks like: coverage.xabierlameiro.com