Skip to content

Commit afec63e

Browse files
committed
feat(learn): Collecting code coverage
1 parent 912b040 commit afec63e

File tree

3 files changed

+303
-1
lines changed

3 files changed

+303
-1
lines changed

apps/site/i18n/locales/en.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@
101101
"links": {
102102
"testRunner": "Test Runner",
103103
"introduction": "Discovering Node.js's test runner",
104-
"usingTestRunner": "Using Node.js's test runner"
104+
"usingTestRunner": "Using Node.js's test runner",
105+
"collectingCodeCoverage": "Collecting code coverage in Node.js"
105106
}
106107
}
107108
},

apps/site/navigation.json

+4
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,10 @@
340340
"usingTestRunner": {
341341
"link": "/learn/test-runner/using-test-runner",
342342
"label": "components.navigation.learn.testRunner.links.usingTestRunner"
343+
},
344+
"collectingCodeCoverage": {
345+
"link": "/learn/test-runner/collecting-code-coverage",
346+
"label": "components.navigation.learn.testRunner.links.collectingCodeCoverage"
343347
}
344348
}
345349
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
---
2+
title: Collecting code coverage in Node.js
3+
layout: learn
4+
authors: RedYetiDev
5+
---
6+
7+
# Collecting code coverage in Node.js
8+
9+
Node.js provides built-in support for code coverage through its test runner, which can be enabled using the `--experimental-code-coverage` flag.
10+
11+
# Basic coverage reporting
12+
13+
Let's walk through a simple example to demonstrate how code coverage works in Node.js.
14+
15+
```javascript displayName="main.js"
16+
function add(a, b) {
17+
return a + b;
18+
}
19+
20+
function isEven(num) {
21+
return num % 2 === 0;
22+
}
23+
24+
function multiply(a, b) {
25+
return a * b;
26+
}
27+
28+
module.exports = { add, isEven, multiply };
29+
```
30+
31+
```js displayName="main.test.js"
32+
const { add, isEven } = require('./main.js');
33+
const { test } = require('node:test');
34+
35+
test('add() should add two numbers', t => {
36+
t.assert.strictEqual(add(1, 2), 3);
37+
});
38+
39+
test('isEven() should report whether a number is even', t => {
40+
t.assert.ok(isEven(0));
41+
});
42+
```
43+
44+
In the module, we have three functions: `add`, `isEven`, and `multiply`.
45+
46+
In the test file, we are testing the `add()` and `isEven()` functions. Notice that the `multiply()` function is not covered by any tests.
47+
48+
To collect code coverage while running your tests, use the following command:
49+
50+
```bash
51+
node --experimental-test-coverage --test main.test.js
52+
```
53+
54+
After running the tests, you'll receive a report that looks something like this:
55+
56+
```text
57+
✔ add() should add two numbers (1.505987ms)
58+
✔ isEven() should report whether a number is even (0.175859ms)
59+
ℹ tests 2
60+
ℹ suites 0
61+
ℹ pass 2
62+
ℹ fail 0
63+
ℹ cancelled 0
64+
ℹ skipped 0
65+
ℹ todo 0
66+
ℹ duration_ms 59.480373
67+
ℹ start of coverage report
68+
ℹ -------------------------------------------------------------
69+
ℹ file | line % | branch % | funcs % | uncovered lines
70+
ℹ -------------------------------------------------------------
71+
ℹ main.js | 76.92 | 100.00 | 66.67 | 9-11
72+
ℹ main.test.js | 100.00 | 100.00 | 100.00 |
73+
ℹ -------------------------------------------------------------
74+
ℹ all files | 86.96 | 100.00 | 80.00 |
75+
ℹ -------------------------------------------------------------
76+
ℹ end of coverage report
77+
```
78+
79+
The coverage report provides a breakdown of how much of your code is covered by tests:
80+
81+
- **Line Coverage**: The percentage of lines executed during the tests.
82+
- **Branch Coverage**: The percentage of code branches (like if-else statements) tested.
83+
- **Function Coverage**: The percentage of functions that have been invoked during testing.
84+
85+
In this example:
86+
87+
- `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.
88+
- `main.test.js` shows 100% coverage across all metrics, indicating that the tests themselves were fully executed.
89+
90+
## Including and excluding
91+
92+
When working on applications, you might encounter situations where certain files or lines of code need to be excluded.
93+
94+
Node.js provides mechanisms to handle this, including the use of comments to ignore specific code sections and the CLI to exclude entire patterns.
95+
96+
### Using comments
97+
98+
```js displayName="main.js"
99+
function add(a, b) {
100+
return a + b;
101+
}
102+
103+
function isEven(num) {
104+
return num % 2 === 0;
105+
}
106+
107+
/* node:coverage ignore next 3 */
108+
function multiply(a, b) {
109+
return a * b;
110+
}
111+
112+
module.exports = { add, isEven, multiply };
113+
```
114+
115+
```text displayName="Coverage Report"
116+
✔ add() should add two numbers (1.430634ms)
117+
✔ isEven() should report whether a number is even (0.202118ms)
118+
ℹ tests 2
119+
ℹ suites 0
120+
ℹ pass 2
121+
ℹ fail 0
122+
ℹ cancelled 0
123+
ℹ skipped 0
124+
ℹ todo 0
125+
ℹ duration_ms 60.507104
126+
ℹ start of coverage report
127+
ℹ -------------------------------------------------------------
128+
ℹ file | line % | branch % | funcs % | uncovered lines
129+
ℹ -------------------------------------------------------------
130+
ℹ main.js | 100.00 | 100.00 | 100.00 |
131+
ℹ main.test.js | 100.00 | 100.00 | 100.00 |
132+
ℹ -------------------------------------------------------------
133+
ℹ all files | 100.00 | 100.00 | 100.00 |
134+
ℹ -------------------------------------------------------------
135+
ℹ end of coverage report
136+
```
137+
138+
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.
139+
140+
There are multiple ways to ignore sections of code using comments.
141+
142+
```js displayName="ignore next"
143+
function add(a, b) {
144+
return a + b;
145+
}
146+
147+
function isEven(num) {
148+
return num % 2 === 0;
149+
}
150+
151+
/* node:coverage ignore next 3 */
152+
function multiply(a, b) {
153+
return a * b;
154+
}
155+
156+
module.exports = { add, isEven, multiply };
157+
```
158+
159+
```js displayName="ignore next"
160+
function add(a, b) {
161+
return a + b;
162+
}
163+
164+
function isEven(num) {
165+
return num % 2 === 0;
166+
}
167+
168+
/* node:coverage ignore next */
169+
function multiply(a, b) {
170+
/* node:coverage ignore next */
171+
return a * b;
172+
/* node:coverage ignore next */
173+
}
174+
175+
module.exports = { add, isEven, multiply };
176+
```
177+
178+
```js displayName="disable"
179+
function add(a, b) {
180+
return a + b;
181+
}
182+
183+
function isEven(num) {
184+
return num % 2 === 0;
185+
}
186+
187+
/* node:coverage disable */
188+
function multiply(a, b) {
189+
return a * b;
190+
}
191+
/* node:coverage enable */
192+
193+
module.exports = { add, isEven, multiply };
194+
```
195+
196+
Each of these different methods will produce the same report, with 100% code coverage across all metrics.
197+
198+
### Using the CLI
199+
200+
Node.js offers two CLI arguments for managing the inclusion or exclusion of specific files in a coverage report.
201+
202+
The `--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.
203+
204+
The `--test-coverage-exclude` flag omits files that match the given glob pattern from the coverage report.
205+
206+
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.
207+
208+
```text displayName="Directory Structure"
209+
.
210+
├── main.test.js
211+
├── src
212+
│   ├── age.js
213+
│   └── name.js
214+
```
215+
216+
```text displayName="Coverage Report"
217+
ℹ start of coverage report
218+
ℹ -------------------------------------------------------------
219+
ℹ file | line % | branch % | funcs % | uncovered lines
220+
ℹ -------------------------------------------------------------
221+
ℹ main.test.js | 100.00 | 100.00 | 100.00 |
222+
ℹ src/age.js | 45.45 | 100.00 | 0.00 | 3-5 7-9
223+
ℹ src/name.js | 100.00 | 100.00 | 100.00 |
224+
ℹ -------------------------------------------------------------
225+
ℹ all files | 88.68 | 100.00 | 75.00 |
226+
ℹ -------------------------------------------------------------
227+
ℹ end of coverage report
228+
```
229+
230+
`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.
231+
232+
```bash displayName="Command"
233+
node --experimental-test-coverage --test-coverage-exclude=src/age.js --test main.test.js
234+
```
235+
236+
```text displayName="New coverage report"
237+
ℹ start of coverage report
238+
ℹ -------------------------------------------------------------
239+
ℹ file | line % | branch % | funcs % | uncovered lines
240+
ℹ -------------------------------------------------------------
241+
ℹ main.test.js | 100.00 | 100.00 | 100.00 |
242+
ℹ src/name.js | 100.00 | 100.00 | 100.00 |
243+
ℹ -------------------------------------------------------------
244+
ℹ all files | 100.00 | 100.00 | 100.00 |
245+
ℹ -------------------------------------------------------------
246+
ℹ end of coverage report
247+
```
248+
249+
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.
250+
251+
```bash displayName="Command"
252+
node --experimental-test-coverage --test-coverage-include=src/*.js --test main.test.js
253+
```
254+
255+
```text displayName="New coverage report"
256+
ℹ start of coverage report
257+
ℹ ------------------------------------------------------------
258+
ℹ file | line % | branch % | funcs % | uncovered lines
259+
ℹ ------------------------------------------------------------
260+
ℹ src/age.js | 45.45 | 100.00 | 0.00 | 3-5 7-9
261+
ℹ src/name.js | 100.00 | 100.00 | 100.00 |
262+
ℹ ------------------------------------------------------------
263+
ℹ all files | 72.73 | 100.00 | 66.67 |
264+
ℹ ------------------------------------------------------------
265+
ℹ end of coverage report
266+
```
267+
268+
## Thresholds
269+
270+
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.
271+
272+
Node.js currently supports thresholds for all three of the coverages supported:
273+
274+
- `--test-coverage-lines` for line coverage.
275+
- `--test-coverage-branches` for branch coverage.
276+
- `--test-coverage-functions` for function coverage.
277+
278+
If you wanted to require the previous example to have line coverage >= 90%, you could use the `--test-coverage-lines=90` flag.
279+
280+
```bash displayName="Command"
281+
node --experimental-test-coverage --test-coverage-lines=75 --test main.test.js
282+
```
283+
284+
```text displayName="Coverage Report"
285+
ℹ start of coverage report
286+
ℹ -------------------------------------------------------------
287+
ℹ file | line % | branch % | funcs % | uncovered lines
288+
ℹ -------------------------------------------------------------
289+
ℹ main.test.js | 100.00 | 100.00 | 100.00 |
290+
ℹ src/age.js | 45.45 | 100.00 | 0.00 | 3-5 7-9
291+
ℹ src/name.js | 100.00 | 100.00 | 100.00 |
292+
ℹ -------------------------------------------------------------
293+
ℹ all files | 88.68 | 100.00 | 75.00 |
294+
ℹ -------------------------------------------------------------
295+
ℹ end of coverage report
296+
ℹ Error: 88.68% line coverage does not meet threshold of 90%.
297+
```

0 commit comments

Comments
 (0)