Merging coverage reports from Jest and Cypress
Code coverage is a great metric to know what code your tests aren't touching, so you can have some insight on potential locations where you can focus on writing new tests. It is common to use Jest to run integration tests (with something like react-testing-library) and Cypress to run E2E tests, but both have different configurations to emit coverage reports, and they are separated. How could we merge them into a single one, for easy visualization?
The first time I tried to do this it got very complex where I utilized some libs from instanbul, but I found this repo from @bahmutov and realized it is actually very simple. First, we need to configure both Jest and Cypress to output the coverage in the right places.
Configuring Jest
Jest already has coverage built-in, we only need to configure it. Here is a snippet from package.json
:
"jest": {
"collectCoverage": true,
"coverageDirectory": "jest-coverage",
"coverageReporters": ["json"]
},
For what we are doing, we only need the json
format when exporting the coverage. I added the collectCoverage
flag on the configuration, but you can also pass it as an option on the CLI with --coverage
. This way you can still have some code that will run the tests without coverage.
Configuring Cypress
Cypress is a bit harder, it doesn't have coverage build-in so we need to set up it ourselves. First install @cypress/code-coverage
, babel-plugin-istanbul
, istanbul-lib-coverage
and nyc
on devDependencies. Explaining each lib:
@cypress/code-coverage
is the plugin library that will integrate the coverage to Cypressistanbul-lib-coverage
andnyc
are peer dependencies of@cypress/code-coverage
babel-plugin-istanbul
is to instrument our code, making it possible to know which line has been executed when running them on Cypress
Now on the cypress/support/index.js
file, add the following content (yes, only that):
import '@cypress/code-coverage/support';
And on cypress/plugins/index.js
:
module.exports = (on, config) => {
on('task', require('@cypress/code-coverage/task'));
on(
'file:preprocessor',
require('@cypress/code-coverage/use-browserify-istanbul')
);
};
Since we are saving the Jest reports in jest-coverage
, we need to edit the nyc
config to save the Cypress reports to cypress-coverage
(yeah, consistency!). Add this to package.json
:
"nyc": {
"report-dir": "cypress-coverage"
}
Now if you run your tests on Cypress, the reports should be in the correct folder.
Merging the reports
Ok, we have both reports on jest-coverage
and cypress-coverage
folders, now how do we merge them?
There are two nyc
commands that can help with this: nyc merge
(will merge all reports inside a folder into a file) and nyc report
(will get the contents of .nyc_output
folder and create the final report). You may have noticed that we need to have the reports in the same folder, and we don't have a .nyc_output
yet. You can automate all these steps using only npm scripts but I preferred to write a Node script to do all of that:
/**
* This script merges the coverage reports from Cypress and Jest into a single one,
* inside the "coverage" folder
*/
const { execSync } = require('child_process');
const fs = require('fs-extra');
const REPORTS_FOLDER = 'reports';
const FINAL_OUTPUT_FOLDER = 'coverage';
const run = (commands) => {
commands.forEach((command) => execSync(command, { stdio: 'inherit' }));
};
// Create the reports folder and move the reports from cypress and jest inside it
fs.emptyDirSync(REPORTS_FOLDER);
fs.copyFileSync(
'cypress-coverage/coverage-final.json',
`${REPORTS_FOLDER}/from-cypress.json`
);
fs.copyFileSync(
'jest-coverage/coverage-final.json',
`${REPORTS_FOLDER}/from-jest.json`
);
fs.emptyDirSync('.nyc_output');
fs.emptyDirSync(FINAL_OUTPUT_FOLDER);
// Run "nyc merge" inside the reports folder, merging the two coverage files into one,
// then generate the final report on the coverage folder
run([
// "nyc merge" will create a "coverage.json" file on the root, we move it to .nyc_output
`nyc merge ${REPORTS_FOLDER} && mv coverage.json .nyc_output/out.json`,
`nyc report --reporter lcov --report-dir ${FINAL_OUTPUT_FOLDER}`,
]);
This script will:
- Create or clear the
reports
folder - Copy the files from
cypress-coverage
andjest-coverage
into thereports
folder - Create or clear the
.nyc_output
andcoverage
folders - Run the
nyc merge
command, that will generate acoverage.json
file at the root of the project, and move it inside.nyc_output
- Run the
nyc report
command, that will get the.nyc_output/out.json
file and generate the full report inside thecoverage
folder
And that's it! You may also want to delete all the created temp folders (besides coverage
) at the end of the process. I just added them to my .gitignore
. Now you can run the script after you finished running all the tests, like this:
"scripts": {
"test-coverage": "yarn test:jest && yarn test:cypress && node ./scripts/mergeCoverage.js"
}
To run the script individually, you can always directly execute it in your terminal:
$ node ./scripts/mergeCoverage.js
All the relevant code is here, besides the above script: https://github.com/bahmutov/cypress-and-jest