Skip to content

Commit 3143566

Browse files
cjihrigRafaelGSS
authored andcommitted
test_runner: add assert.register() API
This commit adds a top level assert.register() API to the test runner. This function allows users to define their own custom assertion functions on the TestContext. Fixes: #52033 PR-URL: #56434 Reviewed-By: Jacob Smith <jacob@frende.me> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Pietro Marchini <pietro.marchini94@gmail.com>
1 parent 332ce54 commit 3143566

File tree

5 files changed

+166
-34
lines changed

5 files changed

+166
-34
lines changed

doc/api/test.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1748,6 +1748,29 @@ describe('tests', async () => {
17481748
});
17491749
```
17501750

1751+
## `assert`
1752+
1753+
<!-- YAML
1754+
added: REPLACEME
1755+
-->
1756+
1757+
An object whose methods are used to configure available assertions on the
1758+
`TestContext` objects in the current process. The methods from `node:assert`
1759+
and snapshot testing functions are available by default.
1760+
1761+
It is possible to apply the same configuration to all files by placing common
1762+
configuration code in a module
1763+
preloaded with `--require` or `--import`.
1764+
1765+
### `assert.register(name, fn)`
1766+
1767+
<!-- YAML
1768+
added: REPLACEME
1769+
-->
1770+
1771+
Defines a new assertion function with the provided name and function. If an
1772+
assertion already exists with the same name, it is overwritten.
1773+
17511774
## `snapshot`
17521775

17531776
<!-- YAML

lib/internal/test_runner/assert.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'use strict';
2+
const {
3+
SafeMap,
4+
} = primordials;
5+
const {
6+
validateFunction,
7+
validateString,
8+
} = require('internal/validators');
9+
const assert = require('assert');
10+
const methodsToCopy = [
11+
'deepEqual',
12+
'deepStrictEqual',
13+
'doesNotMatch',
14+
'doesNotReject',
15+
'doesNotThrow',
16+
'equal',
17+
'fail',
18+
'ifError',
19+
'match',
20+
'notDeepEqual',
21+
'notDeepStrictEqual',
22+
'notEqual',
23+
'notStrictEqual',
24+
'partialDeepStrictEqual',
25+
'rejects',
26+
'strictEqual',
27+
'throws',
28+
];
29+
let assertMap;
30+
31+
function getAssertionMap() {
32+
if (assertMap === undefined) {
33+
assertMap = new SafeMap();
34+
35+
for (let i = 0; i < methodsToCopy.length; i++) {
36+
assertMap.set(methodsToCopy[i], assert[methodsToCopy[i]]);
37+
}
38+
}
39+
40+
return assertMap;
41+
}
42+
43+
function register(name, fn) {
44+
validateString(name, 'name');
45+
validateFunction(fn, 'fn');
46+
const map = getAssertionMap();
47+
map.set(name, fn);
48+
}
49+
50+
module.exports = { getAssertionMap, register };

lib/internal/test_runner/test.js

Lines changed: 18 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -100,34 +100,15 @@ function lazyFindSourceMap(file) {
100100

101101
function lazyAssertObject(harness) {
102102
if (assertObj === undefined) {
103-
assertObj = new SafeMap();
104-
const assert = require('assert');
105-
const { SnapshotManager } = require('internal/test_runner/snapshot');
106-
const methodsToCopy = [
107-
'deepEqual',
108-
'deepStrictEqual',
109-
'doesNotMatch',
110-
'doesNotReject',
111-
'doesNotThrow',
112-
'equal',
113-
'fail',
114-
'ifError',
115-
'match',
116-
'notDeepEqual',
117-
'notDeepStrictEqual',
118-
'notEqual',
119-
'notStrictEqual',
120-
'partialDeepStrictEqual',
121-
'rejects',
122-
'strictEqual',
123-
'throws',
124-
];
125-
for (let i = 0; i < methodsToCopy.length; i++) {
126-
assertObj.set(methodsToCopy[i], assert[methodsToCopy[i]]);
127-
}
103+
const { getAssertionMap } = require('internal/test_runner/assert');
104+
105+
assertObj = getAssertionMap();
106+
if (!assertObj.has('snapshot')) {
107+
const { SnapshotManager } = require('internal/test_runner/snapshot');
128108

129-
harness.snapshotManager = new SnapshotManager(harness.config.updateSnapshots);
130-
assertObj.set('snapshot', harness.snapshotManager.createAssert());
109+
harness.snapshotManager = new SnapshotManager(harness.config.updateSnapshots);
110+
assertObj.set('snapshot', harness.snapshotManager.createAssert());
111+
}
131112
}
132113
return assertObj;
133114
}
@@ -264,15 +245,18 @@ class TestContext {
264245
};
265246
});
266247

267-
// This is a hack. It allows the innerOk function to collect the stacktrace from the correct starting point.
268-
function ok(...args) {
269-
if (plan !== null) {
270-
plan.actual++;
248+
if (!map.has('ok')) {
249+
// This is a hack. It allows the innerOk function to collect the
250+
// stacktrace from the correct starting point.
251+
function ok(...args) {
252+
if (plan !== null) {
253+
plan.actual++;
254+
}
255+
innerOk(ok, args.length, ...args);
271256
}
272-
innerOk(ok, args.length, ...args);
273-
}
274257

275-
assert.ok = ok;
258+
assert.ok = ok;
259+
}
276260
}
277261
return this.#assert;
278262
}

lib/test.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,15 @@ ObjectDefineProperty(module.exports, 'snapshot', {
6161
return lazySnapshot;
6262
},
6363
});
64+
65+
ObjectDefineProperty(module.exports, 'assert', {
66+
__proto__: null,
67+
configurable: true,
68+
enumerable: true,
69+
get() {
70+
const { register } = require('internal/test_runner/assert');
71+
const assert = { __proto__: null, register };
72+
ObjectDefineProperty(module.exports, 'assert', assert);
73+
return assert;
74+
},
75+
});
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('node:assert');
4+
const { test, assert: testAssertions } = require('node:test');
5+
6+
testAssertions.register('isOdd', (n) => {
7+
assert.strictEqual(n % 2, 1);
8+
});
9+
10+
testAssertions.register('ok', () => {
11+
return 'ok';
12+
});
13+
14+
testAssertions.register('snapshot', () => {
15+
return 'snapshot';
16+
});
17+
18+
testAssertions.register('deepStrictEqual', () => {
19+
return 'deepStrictEqual';
20+
});
21+
22+
testAssertions.register('context', function() {
23+
return this;
24+
});
25+
26+
test('throws if name is not a string', () => {
27+
assert.throws(() => {
28+
testAssertions.register(5);
29+
}, {
30+
code: 'ERR_INVALID_ARG_TYPE',
31+
message: 'The "name" argument must be of type string. Received type number (5)'
32+
});
33+
});
34+
35+
test('throws if fn is not a function', () => {
36+
assert.throws(() => {
37+
testAssertions.register('foo', 5);
38+
}, {
39+
code: 'ERR_INVALID_ARG_TYPE',
40+
message: 'The "fn" argument must be of type function. Received type number (5)'
41+
});
42+
});
43+
44+
test('invokes a custom assertion as part of the test plan', (t) => {
45+
t.plan(2);
46+
t.assert.isOdd(5);
47+
assert.throws(() => {
48+
t.assert.isOdd(4);
49+
}, {
50+
code: 'ERR_ASSERTION',
51+
message: /Expected values to be strictly equal/
52+
});
53+
});
54+
55+
test('can override existing assertions', (t) => {
56+
assert.strictEqual(t.assert.ok(), 'ok');
57+
assert.strictEqual(t.assert.snapshot(), 'snapshot');
58+
assert.strictEqual(t.assert.deepStrictEqual(), 'deepStrictEqual');
59+
});
60+
61+
test('"this" is set to the TestContext', (t) => {
62+
assert.strictEqual(t.assert.context(), t);
63+
});

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy