Skip to content

Commit 15bb0be

Browse files
santigimenoMyles Borins
authored and
Myles Borins
committed
doc,test: add How to write a Node.js test guide
PR-URL: #6984 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Rich Trott <[email protected]> Reviewed-By: Yorkie Liu <[email protected]>
1 parent 644bfe1 commit 15bb0be

File tree

2 files changed

+189
-2
lines changed

2 files changed

+189
-2
lines changed

CONTRIBUTING.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,9 @@ $ git rebase upstream/master
140140
### Step 5: Test
141141

142142
Bug fixes and features **should come with tests**. Add your tests in the
143-
test/parallel/ directory. Look at other tests to see how they should be
144-
structured (license boilerplate, common includes, etc.).
143+
`test/parallel/` directory. For guidance on how to write a test for the Node.js
144+
project, see this [guide](./doc/guides/writing_tests.md). Looking at other tests
145+
to see how they should be structured can also help.
145146

146147
```text
147148
$ ./configure && make -j8 test

doc/guides/writing_tests.md

+186
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# How to write a test for the Node.js project
2+
3+
## What is a test?
4+
5+
A test must be a node script that exercises a specific functionality provided
6+
by node and checks that it behaves as expected. It should return 0 on success,
7+
otherwise it will fail. A test will fail if:
8+
9+
- It exits by calling `process.exit(code)` where `code != 0`
10+
- It exits due to an uncaught exception.
11+
- It never exits. In this case, the test runner will terminate the test because
12+
it sets a maximum time limit.
13+
14+
Tests can be added for multiple reasons:
15+
16+
- When adding new functionality.
17+
- When fixing regressions and bugs.
18+
- When expanding test coverage.
19+
20+
21+
## Test structure
22+
23+
Let's analyze this very basic test from the Node.js test suite:
24+
25+
```javascript
26+
1 'use strict';
27+
2 const common = require('../common');
28+
3 const http = require('http');
29+
4 const assert = require('assert');
30+
5
31+
6 const server = http.createServer(common.mustCall((req, res) => {
32+
7 res.end('ok');
33+
8 }));
34+
9 server.listen(common.PORT, () => {
35+
10 http.get({
36+
11 port: common.PORT,
37+
12 headers: {'Test': 'Düsseldorf'}
38+
13 }, common.mustCall((res) => {
39+
14 assert.equal(res.statusCode, 200);
40+
15 server.close();
41+
16 }));
42+
17 });
43+
```
44+
45+
**Lines 1-2**
46+
47+
```javascript
48+
'use strict';
49+
const common = require('../common');
50+
```
51+
52+
These two lines are mandatory and should be included on every test.
53+
The `common` module is a helper module that provides useful tools for the tests.
54+
If for some reason, no functionality from `common` is used, it should still be
55+
included like this:
56+
57+
```javascript
58+
require('../common');
59+
```
60+
61+
Why? It checks for leaks of globals.
62+
63+
**Lines 3-4**
64+
65+
```javascript
66+
const http = require('http');
67+
const assert = require('assert');
68+
```
69+
70+
These modules are required for the test to run. Except for special cases, these
71+
modules should only include core modules.
72+
The `assert` module is used by most of the tests to check that the assumptions
73+
for the test are met.
74+
75+
**Lines 6-17**
76+
77+
This is the body of the test. This test is quite simple, it just tests that an
78+
HTTP server accepts `non-ASCII` characters in the headers of an incoming
79+
request. Interesting things to notice:
80+
81+
- The use of `common.PORT` as the listening port. Always use `common.PORT`
82+
instead of using an arbitrary value, as it allows to run tests in parallel
83+
safely, as they are not trying to reuse the same port another test is already
84+
using.
85+
- The use of `common.mustCall` to check that some callbacks/listeners are
86+
called.
87+
- The HTTP server is closed once all the checks have run. This way, the test can
88+
exit gracefully. Remember that for a test to succeed, it must exit with a
89+
status code of 0.
90+
91+
## General recommendations
92+
93+
### Timers
94+
95+
The use of timers is discouraged, unless timers are being tested. There are
96+
multiple reasons for this. Mainly, they are a source of flakiness. For a thorough
97+
explanation go [here](https://github.com/nodejs/testing/issues/27).
98+
99+
In the event a timer is needed, it's recommended using the
100+
`common.platformTimeout()` method, that allows setting specific timeouts
101+
depending on the platform. For example:
102+
103+
```javascript
104+
const timer = setTimeout(fail, common.platformTimeout(4000));
105+
```
106+
107+
will create a 4-seconds timeout, except for some platforms where the delay will
108+
be multiplied for some factor.
109+
110+
### The *common* API
111+
112+
Make use of the helpers from the `common` module as much as possible.
113+
114+
One interesting case is `common.mustCall`. The use of `common.mustCall` may
115+
avoid the use of extra variables and the corresponding assertions. Let's explain
116+
this with a real test from the test suite.
117+
118+
```javascript
119+
'use strict';
120+
var common = require('../common');
121+
var assert = require('assert');
122+
var http = require('http');
123+
124+
var request = 0;
125+
var response = 0;
126+
process.on('exit', function() {
127+
assert.equal(request, 1, 'http server "request" callback was not called');
128+
assert.equal(response, 1, 'http request "response" callback was not called');
129+
});
130+
131+
var server = http.createServer(function(req, res) {
132+
request++;
133+
res.end();
134+
}).listen(common.PORT, function() {
135+
var options = {
136+
agent: null,
137+
port: this.address().port
138+
};
139+
http.get(options, function(res) {
140+
response++;
141+
res.resume();
142+
server.close();
143+
});
144+
});
145+
```
146+
147+
This test could be greatly simplified by using `common.mustCall` like this:
148+
149+
```javascript
150+
'use strict';
151+
var common = require('../common');
152+
var assert = require('assert');
153+
var http = require('http');
154+
155+
var server = http.createServer(common.mustCall(function(req, res) {
156+
res.end();
157+
})).listen(common.PORT, function() {
158+
var options = {
159+
agent: null,
160+
port: this.address().port
161+
};
162+
http.get(options, common.mustCall(function(res) {
163+
res.resume();
164+
server.close();
165+
}));
166+
});
167+
168+
```
169+
170+
### Flags
171+
172+
Some tests will require running Node.js with specific command line flags set. To
173+
accomplish this, a `// Flags: ` comment should be added in the preamble of the
174+
test followed by the flags. For example, to allow a test to require some of the
175+
`internal/*` modules, the `--expose-internals` flag should be added.
176+
A test that would require `internal/freelist` could start like this:
177+
178+
```javascript
179+
'use strict';
180+
181+
// Flags: --expose-internals
182+
183+
require('../common');
184+
const assert = require('assert');
185+
const freelist = require('internal/freelist');
186+
```

0 commit comments

Comments
 (0)