๐ฆ you can find the source code for this blog post in the repository bahmutov/next-ts-app and the deployed application at https://next-ts-app-swart.vercel.app/. You can find the tests in the separate repo bahmutov/next-ts-app-tests.
- The application
- Instrument when necessary
- Cypress tests with code coverage report
- Deployment and testing
- Tests in a separate repo
- See more
The application
I have scaffolded the Next.js application using the recommended command
1 | $ npx create-next-app@latest --typescript |
There are two modes for running the application: the dev and the prod. I would like to instrument the application in both modes. Thus I have added the following .babelrc
file
1 | { |
I have installed the babel-plugin-istanbul@6
NPM module and if everything works, then starting npm run dev
and opening localhost:3000
shows the code coverage counters under window.__coverage__
object
Instrument when necessary
We want to instrument the app when necessary, thus the simplest way is to look at an environment variable. I have renamed the .babelrc
file into .babelrc.js
file to include the Istanbul plugin only when the environment variable INSTRUMENT_CODE
is present.
1 | const shouldInstrumentCode = 'INSTRUMENT_CODE' in process.env |
I am using Vercel to run the application, and I set this variable to have the code coverage counters present in the deployed code.
Note: code coverage can add some overhead to the production application, so decide if it is worth it. You can still instrument the dev builds and run the end-to-end tests to collect the code coverage, while keeping the production build lean.
Tip: to make sure the instrumentation really regenerates the bundles, delete the .next
folder before running. In my package.json I have the following scripts for running locally
1 | { |
Locally I use npm run dev:instrumented
to launch the instrumented application.
Cypress tests with code coverage report
Let's install Cypress test runner and its code coverage plugin
1 | $ npm i -D cypress @cypress/code-coverage |
I have registered the code coverage report in the plugins file
1 | module.exports = (on, config) => { |
and loaded the plugin from the support file
1 | import '@cypress/code-coverage/support' |
My test is simple: just visiting the site defined in the cypress.json
as baseUrl: http://localhost:3000
1 | it('loads the home page', () => { |
In the Cypress Command Log I see the code coverage report messages.
If you are not sure where the generated report is saved, open the DevTools console and click on the last message. It shows the report was written in the "coverage" folder.
There are coverage reports in various formats
1 | $ ls -la coverage |
I am interested in the HTML report showing code coverage on top of the source files.
1 | $ open coverage/lcov-report/index.html |
The top level report shows 75% of all instrumented statements executed by the cy.visit('/')
command.
We can click on the filename to see the individual coverage report
Deployment and testing
I have set up my Next.js application to deploy on Vercel. You can find the production version of the application at https://next-ts-app-swart.vercel.app/. The INSTRUMENT_CODE
environment variable is set during the Vercel build, thus you can see the code coverage object if you open the DevTools.
Take a look that the source paths in the code coverage object in the deployed production application (marked with an orange arrow). The source paths are different from the source paths to the files when running locally. Let's run the tests to see if we can correctly generate the test coverage report from this coverage object. I will open Cypress test runner pointing at the deployed URL
1 | $ CYPRESS_baseUrl=https://next-ts-app-swart.vercel.app/ npx cypress open |
The tests finish and generate the code coverage report.
The code coverage plugin has successfully mapped the production code paths to the local source files and generated the report
We can see the source code "search" and mapping from the production paths to the local application source paths by enabling the debug logs when starting Cypress
1 | $ DEBUG=code-coverage CYPRESS_baseUrl=https://next-ts-app-swart.vercel.app/ npx cypress open |
The logs show how the plugin is looking for a parent folder so that all paths in the code coverage object map to the existing file paths.
To generate the report we need to code coverage information and the application source files.
Tests in a separate repo
In some situations, the tests live in a repository separate from the application. I have described such situation in the blog posts How to Keep Cypress Tests in Another Repo While Using GitHub Actions and How to Keep Cypress Tests in Another Repo While Using CircleCI. For this blog post, I have created repository bahmutov/next-ts-app-tests with a copy of Cypress tests. We can run these tests against the deployed application
1 | $ DEBUG=code-coverage CYPRESS_baseUrl=https://next-ts-app-swart.vercel.app/ npx cypress open |
This time, the code coverage cannot be mapped to the source files, since there are no local files to find
The plugin has still generated the overall report, but you cannot drill down into the individual source file reports
If we copy just the pages
folder from the next-ts-app into the "next-ts-app-tests" folder before running the Cypress tests, then it finds it and can generate the report.
Tip: instead of copying the pages
folder from the application's folder to the test folder, I create a symbolic link
1 | # assuming the following structure |
Now the "pages" folder is linked to the tests folder
1 | $ ls -la |
Cloning the app repo into the tests repo
Let's give our tests the application's source code so it can generate the file code coverage reports. I will use GitHub Actions to checkout out both repos and copy the "pages" folder from the application folder into the tests folder.
1 | name: ci |
Tip: I have used actions/github-pages-action step at the end to publish the generated HTML code coverage report to GitHub Pages. You can find it at https://glebbahmutov.com/next-ts-app-tests/.
Fetching the right application source code
Imagine you are deploying an instrumented application to some environment, like https://instrumented.acme.co
once per day. Then you run the tests against it to generate the full code coverage report. You only do this once per day because instrumenting and running the tests is slow, but there might be multiple commits to the application source code itself. How do you use the right source code when generating the coverage reports? By checking out the right source code commit for the deployed application.
Next.js applications embed the buildId
in the pages, and you can control the ID. For example, you can concatenate the branch and the source code commit SHA, like this
1 | // next.config.js |
I wrote a little GitHub action to query the HTML page and extract the build ID value and split it into branch and commit SHA. Then you can check out the right source code commit when checking out the application on CI.
1 | # https://github.com/actions/checkout |
Code coverage on CircleCI
I have set up an equivalent code coverage collection on CircleCI. The tests project logs in using a machine user account and checks out the application source code before running the tests and generating the report. See .circleci/config.yml file for the current code
1 | version: 2.1 |
The source code report is stored as a test artifact on CircleCI
Tip: if you do not want to set up SSH key to check out the second repository, you could use a GitHub token
1 | git clone https://${GITHUB_TOKEN}:[email protected]/owner/repo <local folder name> --depth 1 |
See more
- Periodically Update A Branch
- read my other blog posts about code coverage
- see the JavaScript application bahmutov/next-and-cypress-example
- Component Testing Next.js Application with Cypress