• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# How to write a test for the Node.js project
2
3## What is a test?
4
5Most tests in Node.js core are JavaScript programs that exercise a functionality
6provided by Node.js and check that it behaves as expected. Tests should exit
7with code `0` on success. A test will fail if:
8
9* It exits by setting `process.exitCode` to a non-zero number.
10  * This is usually done by having an assertion throw an uncaught Error.
11  * Occasionally, using `process.exit(code)` may be appropriate.
12* It never exits. In this case, the test runner will terminate the test because
13  it sets a maximum time limit.
14
15Add tests when:
16
17* Adding new functionality.
18* Fixing regressions and bugs.
19* Expanding test coverage.
20
21## Test directory structure
22
23See [directory structure overview][] for outline of existing test & locations.
24When deciding on whether to expand an existing test file or create a new one,
25consider going through the files related to the subsystem.
26For example, look for `test-streams` when writing a test for `lib/streams.js`.
27
28## Test structure
29
30Let's analyze this basic test from the Node.js test suite:
31
32```js
33'use strict';                                                          // 1
34const common = require('../common');                                   // 2
35const fixtures = require('../common/fixtures');                        // 3
36
37// This test ensures that the http-parser can handle UTF-8 characters  // 5
38// in the http header.                                                 // 6
39
40const assert = require('assert');                                      // 8
41const http = require('http');                                          // 9
42
43const server = http.createServer(common.mustCall((req, res) => {       // 11
44  res.end('ok');                                                       // 12
45}));                                                                   // 13
46server.listen(0, () => {                                               // 14
47  http.get({                                                           // 15
48    port: server.address().port,                                       // 16
49    headers: { 'Test': 'Düsseldorf' }                                  // 17
50  }, common.mustCall((res) => {                                        // 18
51    assert.strictEqual(res.statusCode, 200);                           // 19
52    server.close();                                                    // 20
53  }));                                                                 // 21
54});                                                                    // 22
55// ...                                                                 // 23
56```
57
58### **Lines 1-3**
59
60```js
61'use strict';
62const common = require('../common');
63const fixtures = require('../common/fixtures');
64```
65
66The first line enables strict mode. All tests should be in strict mode unless
67the nature of the test requires that the test run without it.
68
69The second line loads the `common` module. The [`common` module][] is a helper
70module that provides useful tools for the tests. Some common functionality has
71been extracted into submodules, which are required separately like the fixtures
72module here.
73
74Even if a test uses no functions or other properties exported by `common`,
75the test should still include the `common` module before any other modules. This
76is because the `common` module includes code that will cause a test to fail if
77the test leaks variables into the global space. In situations where a test uses
78no functions or other properties exported by `common`, include it without
79assigning it to an identifier:
80
81```js
82require('../common');
83```
84
85### **Lines 5-6**
86
87```js
88// This test ensures that the http-parser can handle UTF-8 characters
89// in the http header.
90```
91
92A test should start with a comment containing a brief description of what it is
93designed to test.
94
95### **Lines 8-9**
96
97```js
98const assert = require('assert');
99const http = require('http');
100```
101
102The test checks functionality in the `http` module.
103
104Most tests use the `assert` module to confirm expectations of the test.
105
106The require statements are sorted in
107[ASCII][] order (digits, upper
108case, `_`, lower case).
109
110### **Lines 11-22**
111
112This is the body of the test. This test is simple, it just tests that an
113HTTP server accepts `non-ASCII` characters in the headers of an incoming
114request. Interesting things to notice:
115
116* If the test doesn't depend on a specific port number, then always use 0
117  instead of an arbitrary value, as it allows tests to run in parallel safely,
118  as the operating system will assign a random port. If the test requires a
119  specific port, for example if the test checks that assigning a specific port
120  works as expected, then it is ok to assign a specific port number.
121* The use of `common.mustCall` to check that some callbacks/listeners are
122  called.
123* The HTTP server closes once all the checks have run. This way, the test can
124  exit gracefully. Remember that for a test to succeed, it must exit with a
125  status code of 0.
126
127## General recommendations
128
129### Timers
130
131Avoid timers unless the test is specifically testing timers. There are multiple
132reasons for this. Mainly, they are a source of flakiness. For a thorough
133explanation go [here](https://github.com/nodejs/testing/issues/27).
134
135In the event a test needs a timer, consider using the
136`common.platformTimeout()` method. It allows setting specific timeouts
137depending on the platform:
138
139```js
140const timer = setTimeout(fail, common.platformTimeout(4000));
141```
142
143will create a 4-second timeout on most platforms but a longer timeout on slower
144platforms.
145
146### The *common* API
147
148Make use of the helpers from the `common` module as much as possible. Please
149refer to the [common file documentation](https://github.com/nodejs/node/tree/master/test/common)
150for the full details of the helpers.
151
152#### common.mustCall
153
154One interesting case is `common.mustCall`. The use of `common.mustCall` may
155avoid the use of extra variables and the corresponding assertions. Let's
156explain this with a real test from the test suite.
157
158```js
159'use strict';
160require('../common');
161const assert = require('assert');
162const http = require('http');
163
164let request = 0;
165let listening = 0;
166let response = 0;
167process.on('exit', () => {
168  assert.equal(request, 1, 'http server "request" callback was not called');
169  assert.equal(listening, 1, 'http server "listening" callback was not called');
170  assert.equal(response, 1, 'http request "response" callback was not called');
171});
172
173const server = http.createServer((req, res) => {
174  request++;
175  res.end();
176}).listen(0, () => {
177  listening++;
178  const options = {
179    agent: null,
180    port: server.address().port
181  };
182  http.get(options, (res) => {
183    response++;
184    res.resume();
185    server.close();
186  });
187});
188```
189
190This test could be greatly simplified by using `common.mustCall` like this:
191
192```js
193'use strict';
194const common = require('../common');
195const http = require('http');
196
197const server = http.createServer(common.mustCall((req, res) => {
198  res.end();
199})).listen(0, common.mustCall(() => {
200  const options = {
201    agent: null,
202    port: server.address().port
203  };
204  http.get(options, common.mustCall((res) => {
205    res.resume();
206    server.close();
207  }));
208}));
209
210```
211
212#### Countdown Module
213
214The common [Countdown module](https://github.com/nodejs/node/tree/master/test/common#countdown-module)
215provides a simple countdown mechanism for tests that require a particular
216action to be taken after a given number of completed tasks (for instance,
217shutting down an HTTP server after a specific number of requests).
218
219```js
220const Countdown = require('../common/countdown');
221
222const countdown = new Countdown(2, () => {
223  console.log('.');
224});
225
226countdown.dec();
227countdown.dec(); // The countdown callback will be invoked now.
228```
229
230#### Testing promises
231
232When writing tests involving promises, it is generally good to wrap the
233`onFulfilled` handler, otherwise the test could successfully finish if the
234promise never resolves (pending promises do not keep the event loop alive). The
235`common` module automatically adds a handler that makes the process crash - and
236hence, the test fail - in the case of an `unhandledRejection` event. It is
237possible to disable it with `common.disableCrashOnUnhandledRejection()` if
238needed.
239
240```js
241const common = require('../common');
242const assert = require('assert');
243const fs = require('fs').promises;
244
245// Wrap the `onFulfilled` handler in `common.mustCall()`.
246fs.readFile('test-file').then(
247  common.mustCall(
248    (content) => assert.strictEqual(content.toString(), 'test2')
249  ));
250```
251
252### Flags
253
254Some tests will require running Node.js with specific command line flags set. To
255accomplish this, add a `// Flags:` comment in the preamble of the
256test followed by the flags. For example, to allow a test to require some of the
257`internal/*` modules, add the `--expose-internals` flag.
258A test that would require `internal/freelist` could start like this:
259
260```js
261'use strict';
262
263// Flags: --expose-internals
264
265require('../common');
266const assert = require('assert');
267const freelist = require('internal/freelist');
268```
269
270### Assertions
271
272When writing assertions, prefer the strict versions:
273
274* `assert.strictEqual()` over `assert.equal()`
275* `assert.deepStrictEqual()` over `assert.deepEqual()`
276
277When using `assert.throws()`, if possible, provide the full error message:
278
279```js
280assert.throws(
281  () => {
282    throw new Error('Wrong value');
283  },
284  /^Error: Wrong value$/ // Instead of something like /Wrong value/
285);
286```
287
288### Console output
289
290Output written by tests to stdout or stderr, such as with `console.log()` or
291`console.error()`, can be useful when writing tests, as well as for debugging
292them during later maintenance. The output will be suppressed by the test runner
293(`./tools/test.py`) unless the test fails, but will always be displayed when
294running tests directly with `node`. For failing tests, the test runner will
295include the output along with the failed test assertion in the test report.
296
297Some output can help debugging by giving context to test failures. For example,
298when troubleshooting tests that timeout in CI. With no log statements, we have
299no idea where the test got hung up.
300
301There have been cases where tests fail without `console.log()`, and then pass
302when its added, so be cautious about its use, particularly in tests of the I/O
303and streaming APIs.
304
305Excessive use of console output is discouraged as it can overwhelm the display,
306including the Jenkins console and test report displays. Be particularly
307cautious of output in loops, or other contexts where output may be repeated many
308times in the case of failure.
309
310In some tests, it can be unclear whether a `console.log()` statement is required
311as part of the test (message tests, tests that check output from child
312processes, etc.), or is there as a debug aide. If there is any chance of
313confusion, use comments to make the purpose clear.
314
315### ES.Next features
316
317For performance considerations, we only use a selected subset of ES.Next
318features in JavaScript code in the `lib` directory. However, when writing
319tests, for the ease of backporting, it is encouraged to use those ES.Next
320features that can be used directly without a flag in
321[all maintained branches][]. [node.green][] lists available features
322in each release, such as:
323
324* `let` and `const` over `var`
325* Template literals over string concatenation
326* Arrow functions when appropriate
327
328## Naming Test Files
329
330Test files are named using kebab casing. The first component of the name is
331`test`. The second is the module or subsystem being tested. The third is usually
332the method or event name being tested. Subsequent components of the name add
333more information about what is being tested.
334
335For example, a test for the `beforeExit` event on the `process` object might be
336named `test-process-before-exit.js`. If the test specifically checked that arrow
337functions worked correctly with the `beforeExit` event, then it might be named
338`test-process-before-exit-arrow-functions.js`.
339
340## Imported Tests
341
342### Web Platform Tests
343
344See [`test/wpt`](../../test/wpt/README.md) for more information.
345
346## C++ Unit test
347
348C++ code can be tested using [Google Test][]. Most features in Node.js can be
349tested using the methods described previously in this document. But there are
350cases where these might not be enough, for example writing code for Node.js
351that will only be called when Node.js is embedded.
352
353### Adding a new test
354
355The unit test should be placed in `test/cctest` and be named with the prefix
356`test` followed by the name of unit being tested. For example, the code below
357would be placed in `test/cctest/test_env.cc`:
358
359```cpp
360#include "gtest/gtest.h"
361#include "node_test_fixture.h"
362#include "env.h"
363#include "node.h"
364#include "v8.h"
365
366static bool called_cb = false;
367static void at_exit_callback(void* arg);
368
369class EnvTest : public NodeTestFixture { };
370
371TEST_F(EnvTest, RunAtExit) {
372  v8::HandleScope handle_scope(isolate_);
373  v8::Local<v8::Context> context = v8::Context::New(isolate_);
374  node::IsolateData* isolateData = node::CreateIsolateData(isolate_, uv_default_loop());
375  Argv argv{"node", "-e", ";"};
376  auto env = node::CreateEnvironment(isolateData, context, 1, *argv, 2, *argv);
377  node::AtExit(env, at_exit_callback);
378  node::RunAtExit(env);
379  EXPECT_TRUE(called_cb);
380}
381
382static void at_exit_callback(void* arg) {
383  called_cb = true;
384}
385```
386
387Next add the test to the `sources` in the `cctest` target in node.gyp:
388
389```console
390'sources': [
391  'test/cctest/test_env.cc',
392  ...
393],
394```
395
396The only sources that should be included in the cctest target are
397actual test or helper source files. There might be a need to include specific
398object files that are compiled by the `node` target and this can be done by
399adding them to the `libraries` section in the cctest target.
400
401The test can be executed by running the `cctest` target:
402
403```console
404$ make cctest
405```
406
407A filter can be applied to run single/multiple test cases:
408
409```console
410$ make cctest GTEST_FILTER=EnvironmentTest.AtExitWithArgument
411```
412
413`cctest` can also be run directly which can be useful when debugging:
414
415```console
416$ out/Release/cctest --gtest_filter=EnvironmentTest.AtExit*
417```
418
419### Node.js test fixture
420There is a [test fixture][] named `node_test_fixture.h` which can be included by
421unit tests. The fixture takes care of setting up the Node.js environment
422and tearing it down after the tests have finished.
423
424It also contains a helper to create arguments to be passed into Node.js. It
425will depend on what is being tested if this is required or not.
426
427### Test Coverage
428
429To generate a test coverage report, see the
430[Test Coverage section of the Building guide][].
431
432Nightly coverage reports for the Node.js master branch are available at
433<https://coverage.nodejs.org/>.
434
435[ASCII]: https://man7.org/linux/man-pages/man7/ascii.7.html
436[Google Test]: https://github.com/google/googletest
437[Test Coverage section of the Building guide]: https://github.com/nodejs/node/blob/master/BUILDING.md#running-coverage
438[`common` module]: https://github.com/nodejs/node/blob/master/test/common/README.md
439[all maintained branches]: https://github.com/nodejs/lts
440[directory structure overview]: https://github.com/nodejs/node/blob/master/test/README.md#test-directories
441[node.green]: https://node.green/
442[test fixture]: https://github.com/google/googletest/blob/master/googletest/docs/Primer.md#test-fixtures-using-the-same-data-configuration-for-multiple-tests
443