Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(learn): Collecting code coverage #7006

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/site/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@
"links": {
"testRunner": "Test Runner",
"introduction": "Discovering Node.js's test runner",
"usingTestRunner": "Using Node.js's test runner"
"usingTestRunner": "Using Node.js's test runner",
"collectingCodeCoverage": "Collecting code coverage in Node.js"
}
}
},
Expand Down
4 changes: 4 additions & 0 deletions apps/site/navigation.json
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,10 @@
"usingTestRunner": {
"link": "/learn/test-runner/using-test-runner",
"label": "components.navigation.learn.testRunner.links.usingTestRunner"
},
"collectingCodeCoverage": {
"link": "/learn/test-runner/collecting-code-coverage",
"label": "components.navigation.learn.testRunner.links.collectingCodeCoverage"
}
}
}
Expand Down
303 changes: 303 additions & 0 deletions apps/site/pages/en/learn/test-runner/collecting-code-coverage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
---
title: Collecting code coverage in Node.js
layout: learn
authors: RedYetiDev
---

# Collecting code coverage in Node.js

Node.js provides built-in support for code coverage through its test runner, which can be enabled using the [`--experimental-code-coverage`](https://nodejs.org/api/cli.html#--experimental-test-coverage) flag.

avivkeller marked this conversation as resolved.
Show resolved Hide resolved
## What is code coverage?

Code coverage is a metric for test runners that gauges how much of a program’s source code is executed during testing. It reveals which portions of the codebase are tested and which are not, helping to pinpoint gaps in the test suite. This ensures more comprehensive testing of the software and minimizes the risk of undetected bugs. Typically expressed as a percentage, higher code coverage percentages indicate more thorough test coverage. For a more detailed explanation of code coverage, you can refer to the ["Code coverage" Wikipedia article](https://en.wikipedia.org/wiki/Code_coverage).

## Basic coverage reporting

Let's walk through a simple example to demonstrate how code coverage works in Node.js.
avivkeller marked this conversation as resolved.
Show resolved Hide resolved

> **Note:** This example is written using CommonJS. If you are unfamiliar with this concept, please read the [CommonJS Modules](https://nodejs.org/docs/latest/api/modules.html) documentation.

```cjs displayName="main.js"
function add(a, b) {
return a + b;
}

function isEven(num) {
return num % 2 === 0;
}

function multiply(a, b) {
return a * b;
}

module.exports = { add, isEven, multiply };
```
AugustinMauroy marked this conversation as resolved.
Show resolved Hide resolved

```cjs displayName="main.test.js"
const { add, isEven } = require('./main.js');
const { test } = require('node:test');

test('add() should add two numbers', t => {
t.assert.strictEqual(add(1, 2), 3);
});

test('isEven() should report whether a number is even', t => {
t.assert.ok(isEven(0));
});
```

In the module, we have three functions: `add`, `isEven`, and `multiply`.

In the test file, we are testing the `add()` and `isEven()` functions. Notice that the `multiply()` function is not covered by any tests.

To collect code coverage while running your tests, use the following command:

```bash
node --experimental-test-coverage --test main.test.js
```

After running the tests, you'll receive a report that looks something like this:

```text displayName="Coverage Report"
✔ add() should add two numbers (1.505987ms)
✔ isEven() should report whether a number is even (0.175859ms)
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 59.480373
ℹ start of coverage report
ℹ -------------------------------------------------------------
ℹ file | line % | branch % | funcs % | uncovered lines
ℹ -------------------------------------------------------------
ℹ main.js | 76.92 | 100.00 | 66.67 | 9-11
ℹ main.test.js | 100.00 | 100.00 | 100.00 |
ℹ -------------------------------------------------------------
ℹ all files | 86.96 | 100.00 | 80.00 |
ℹ -------------------------------------------------------------
ℹ end of coverage report
```

The coverage report provides a breakdown of how much of your code is covered by tests:

- **Line Coverage**: The percentage of lines executed during the tests.
- **Branch Coverage**: The percentage of code branches (like if-else statements) tested.
- **Function Coverage**: The percentage of functions that have been invoked during testing.

In this example:

- `main.js` shows 76.92% line coverage and 66.67% function coverage because the `multiply()` function was not tested. The uncovered lines (9-11) correspond to this function.
- `main.test.js` shows 100% coverage across all metrics, indicating that the tests themselves were fully executed.

## Including and excluding

When working on applications, you might encounter situations where certain files or lines of code need to be excluded.

Node.js provides mechanisms to handle this, including the use of comments to ignore specific code sections and the CLI to exclude entire patterns.

### Using comments

```cjs displayName="main.js"
function add(a, b) {
return a + b;
}

function isEven(num) {
return num % 2 === 0;
}

/* node:coverage ignore next 3 */
function multiply(a, b) {
return a * b;
}

module.exports = { add, isEven, multiply };
```

```text displayName="Coverage Report"
✔ add() should add two numbers (1.430634ms)
✔ isEven() should report whether a number is even (0.202118ms)
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 60.507104
ℹ start of coverage report
ℹ -------------------------------------------------------------
ℹ file | line % | branch % | funcs % | uncovered lines
ℹ -------------------------------------------------------------
ℹ main.js | 100.00 | 100.00 | 100.00 |
ℹ main.test.js | 100.00 | 100.00 | 100.00 |
ℹ -------------------------------------------------------------
ℹ all files | 100.00 | 100.00 | 100.00 |
ℹ -------------------------------------------------------------
ℹ end of coverage report
```

When reporting coverage with this modified `main.js` file, the report will now show 100% coverage across all metrics. This is because the uncovered lines (9-11) have been ignored.

There are multiple ways to ignore sections of code using comments.

```cjs displayName="ignore next"
function add(a, b) {
return a + b;
}

function isEven(num) {
return num % 2 === 0;
}

/* node:coverage ignore next 3 */
function multiply(a, b) {
return a * b;
}

module.exports = { add, isEven, multiply };
```

```cjs displayName="ignore next"
function add(a, b) {
return a + b;
}

function isEven(num) {
return num % 2 === 0;
}

/* node:coverage ignore next */
function multiply(a, b) {
/* node:coverage ignore next */
return a * b;
/* node:coverage ignore next */
}

module.exports = { add, isEven, multiply };
```

```cjs displayName="disable"
function add(a, b) {
return a + b;
}

function isEven(num) {
return num % 2 === 0;
}

/* node:coverage disable */
function multiply(a, b) {
return a * b;
}
/* node:coverage enable */

module.exports = { add, isEven, multiply };
```

Each of these different methods will produce the same report, with 100% code coverage across all metrics.

### Using the CLI

Node.js offers two CLI arguments for managing the inclusion or exclusion of specific files in a coverage report.

The [`--test-coverage-include`](https://nodejs.org/api/cli.html#--test-coverage-include) flag restricts the coverage to files that match the provided glob pattern. By default, files in the `/node_modules/` directory are excluded, but this flag allows you to explicitly include them.

The [`--test-coverage-exclude`](https://nodejs.org/api/cli.html#--test-coverage-exclude) flag omits files that match the given glob pattern from the coverage report.

These flags can be used multiple times, and when both are used together, files must adhere to the inclusion rules, while also avoiding the exclusion rules.

```text displayName="Directory Structure"
.
├── main.test.js
├── src
│   ├── age.js
│   └── name.js
```

```text displayName="Coverage Report"
ℹ start of coverage report
ℹ -------------------------------------------------------------
ℹ file | line % | branch % | funcs % | uncovered lines
ℹ -------------------------------------------------------------
ℹ main.test.js | 100.00 | 100.00 | 100.00 |
ℹ src/age.js | 45.45 | 100.00 | 0.00 | 3-5 7-9
ℹ src/name.js | 100.00 | 100.00 | 100.00 |
ℹ -------------------------------------------------------------
ℹ all files | 88.68 | 100.00 | 75.00 |
ℹ -------------------------------------------------------------
ℹ end of coverage report
```

`src/age.js` has less-than-optimal coverage in the report above, but with the `--test-coverage-exclude` flag, it can be excluded from the report entirely.

```bash
node --experimental-test-coverage --test-coverage-exclude=src/age.js --test main.test.js
```

```text displayName="New coverage report"
ℹ start of coverage report
ℹ -------------------------------------------------------------
ℹ file | line % | branch % | funcs % | uncovered lines
ℹ -------------------------------------------------------------
ℹ main.test.js | 100.00 | 100.00 | 100.00 |
ℹ src/name.js | 100.00 | 100.00 | 100.00 |
ℹ -------------------------------------------------------------
ℹ all files | 100.00 | 100.00 | 100.00 |
ℹ -------------------------------------------------------------
ℹ end of coverage report
```

Our test file is also included in this coverage report, but we only want JavaScript files in the `src/` directory. The `--test-coverage-include` flag can be used in this case.

```bash
node --experimental-test-coverage --test-coverage-include=src/*.js --test main.test.js
```

```text displayName="New coverage report"
ℹ start of coverage report
ℹ ------------------------------------------------------------
ℹ file | line % | branch % | funcs % | uncovered lines
ℹ ------------------------------------------------------------
ℹ src/age.js | 45.45 | 100.00 | 0.00 | 3-5 7-9
ℹ src/name.js | 100.00 | 100.00 | 100.00 |
ℹ ------------------------------------------------------------
ℹ all files | 72.73 | 100.00 | 66.67 |
ℹ ------------------------------------------------------------
ℹ end of coverage report
```

## Thresholds

By default, when all tests pass, Node.js exits with code `0`, which indicates a successful execution. However, the coverage report can be configured to exit with code `1` when coverage is failing.

Node.js currently supports thresholds for all three of the coverages supported:

- [`--test-coverage-lines`](https://nodejs.org/api/cli.html#--test-coverage-linesthreshold) for line coverage.
- [`--test-coverage-branches`](https://nodejs.org/api/cli.html#--test-coverage-branchesthreshold) for branch coverage.
- [`--test-coverage-functions`](https://nodejs.org/api/cli.html#--test-coverage-functionsthreshold) for function coverage.

If you wanted to require the previous example to have line coverage >= 90%, you could use the `--test-coverage-lines=90` flag.

```bash
node --experimental-test-coverage --test-coverage-lines=75 --test main.test.js
avivkeller marked this conversation as resolved.
Show resolved Hide resolved
```

```text displayName="Coverage Report"
ℹ start of coverage report
ℹ -------------------------------------------------------------
ℹ file | line % | branch % | funcs % | uncovered lines
ℹ -------------------------------------------------------------
ℹ main.test.js | 100.00 | 100.00 | 100.00 |
ℹ src/age.js | 45.45 | 100.00 | 0.00 | 3-5 7-9
ℹ src/name.js | 100.00 | 100.00 | 100.00 |
ℹ -------------------------------------------------------------
ℹ all files | 88.68 | 100.00 | 75.00 |
ℹ -------------------------------------------------------------
ℹ end of coverage report
ℹ Error: 88.68% line coverage does not meet threshold of 90%.
```
Loading