Skip to content

Commit 4ed5d1f

Browse files
MoLowaduh95
authored andcommitted
feat: add before/after/each hooks
PR-URL: nodejs/node#43730 Fixes: nodejs/node#43403 Reviewed-By: Benjamin Gruenbaum <[email protected]> (cherry picked from commit 659dc126932f986fc33c7f1c878cb2b57a1e2fac)
1 parent 215621e commit 4ed5d1f

File tree

12 files changed

+696
-40
lines changed

12 files changed

+696
-40
lines changed

README.md

+162
Original file line numberDiff line numberDiff line change
@@ -440,12 +440,166 @@ same as [`it([name], { skip: true }[, fn])`][it options].
440440
Shorthand for marking a test as `TODO`,
441441
same as [`it([name], { todo: true }[, fn])`][it options].
442442

443+
### `before([, fn][, options])`
444+
445+
* `fn` {Function|AsyncFunction} The hook function.
446+
If the hook uses callbacks,
447+
the callback function is passed as the second argument. **Default:** A no-op
448+
function.
449+
* `options` {Object} Configuration options for the hook. The following
450+
properties are supported:
451+
* `signal` {AbortSignal} Allows aborting an in-progress hook
452+
* `timeout` {number} A number of milliseconds the hook will fail after.
453+
If unspecified, subtests inherit this value from their parent.
454+
**Default:** `Infinity`.
455+
456+
This function is used to create a hook running before running a suite.
457+
458+
```js
459+
describe('tests', async () => {
460+
before(() => console.log('about to run some test'));
461+
it('is a subtest', () => {
462+
assert.ok('some relevant assertion here');
463+
});
464+
});
465+
```
466+
467+
### `after([, fn][, options])`
468+
469+
* `fn` {Function|AsyncFunction} The hook function.
470+
If the hook uses callbacks,
471+
the callback function is passed as the second argument. **Default:** A no-op
472+
function.
473+
* `options` {Object} Configuration options for the hook. The following
474+
properties are supported:
475+
* `signal` {AbortSignal} Allows aborting an in-progress hook
476+
* `timeout` {number} A number of milliseconds the hook will fail after.
477+
If unspecified, subtests inherit this value from their parent.
478+
**Default:** `Infinity`.
479+
480+
This function is used to create a hook running after running a suite.
481+
482+
```js
483+
describe('tests', async () => {
484+
after(() => console.log('finished running tests'));
485+
it('is a subtest', () => {
486+
assert.ok('some relevant assertion here');
487+
});
488+
});
489+
```
490+
491+
### `beforeEach([, fn][, options])`
492+
493+
* `fn` {Function|AsyncFunction} The hook function.
494+
If the hook uses callbacks,
495+
the callback function is passed as the second argument. **Default:** A no-op
496+
function.
497+
* `options` {Object} Configuration options for the hook. The following
498+
properties are supported:
499+
* `signal` {AbortSignal} Allows aborting an in-progress hook
500+
* `timeout` {number} A number of milliseconds the hook will fail after.
501+
If unspecified, subtests inherit this value from their parent.
502+
**Default:** `Infinity`.
503+
504+
This function is used to create a hook running
505+
before each subtest of the current suite.
506+
507+
```js
508+
describe('tests', async () => {
509+
beforeEach(() => t.diagnostics('about to run a test'));
510+
it('is a subtest', () => {
511+
assert.ok('some relevant assertion here');
512+
});
513+
});
514+
```
515+
516+
### `afterEach([, fn][, options])`
517+
518+
* `fn` {Function|AsyncFunction} The hook function.
519+
If the hook uses callbacks,
520+
the callback function is passed as the second argument. **Default:** A no-op
521+
function.
522+
* `options` {Object} Configuration options for the hook. The following
523+
properties are supported:
524+
* `signal` {AbortSignal} Allows aborting an in-progress hook
525+
* `timeout` {number} A number of milliseconds the hook will fail after.
526+
If unspecified, subtests inherit this value from their parent.
527+
**Default:** `Infinity`.
528+
529+
This function is used to create a hook running
530+
after each subtest of the current test.
531+
532+
```js
533+
describe('tests', async () => {
534+
afterEach(() => t.diagnostics('about to run a test'));
535+
it('is a subtest', () => {
536+
assert.ok('some relevant assertion here');
537+
});
538+
});
539+
```
540+
443541
## Class: `TestContext`
444542

445543
An instance of `TestContext` is passed to each test function in order to
446544
interact with the test runner. However, the `TestContext` constructor is not
447545
exposed as part of the API.
448546

547+
### `context.beforeEach([, fn][, options])`
548+
549+
* `fn` {Function|AsyncFunction} The hook function. The first argument
550+
to this function is a [`TestContext`][] object. If the hook uses callbacks,
551+
the callback function is passed as the second argument. **Default:** A no-op
552+
function.
553+
* `options` {Object} Configuration options for the hook. The following
554+
properties are supported:
555+
* `signal` {AbortSignal} Allows aborting an in-progress hook
556+
* `timeout` {number} A number of milliseconds the hook will fail after.
557+
If unspecified, subtests inherit this value from their parent.
558+
**Default:** `Infinity`.
559+
560+
This function is used to create a hook running
561+
before each subtest of the current test.
562+
563+
```js
564+
test('top level test', async (t) => {
565+
t.beforeEach((t) => t.diagnostics(`about to run ${t.name}`));
566+
await t.test(
567+
'This is a subtest',
568+
(t) => {
569+
assert.ok('some relevant assertion here');
570+
}
571+
);
572+
});
573+
```
574+
575+
### `context.afterEach([, fn][, options])`
576+
577+
* `fn` {Function|AsyncFunction} The hook function. The first argument
578+
to this function is a [`TestContext`][] object. If the hook uses callbacks,
579+
the callback function is passed as the second argument. **Default:** A no-op
580+
function.
581+
* `options` {Object} Configuration options for the hook. The following
582+
properties are supported:
583+
* `signal` {AbortSignal} Allows aborting an in-progress hook
584+
* `timeout` {number} A number of milliseconds the hook will fail after.
585+
If unspecified, subtests inherit this value from their parent.
586+
**Default:** `Infinity`.
587+
588+
This function is used to create a hook running
589+
after each subtest of the current test.
590+
591+
```js
592+
test('top level test', async (t) => {
593+
t.afterEach((t) => t.diagnostics(`finished running ${t.name}`));
594+
await t.test(
595+
'This is a subtest',
596+
(t) => {
597+
assert.ok('some relevant assertion here');
598+
}
599+
);
600+
});
601+
```
602+
449603
### `context.diagnostic(message)`
450604

451605
- `message` {string} Message to be displayed as a TAP diagnostic.
@@ -454,6 +608,10 @@ This function is used to write TAP diagnostics to the output. Any diagnostic
454608
information is included at the end of the test's results. This function does
455609
not return a value.
456610

611+
`context.name`
612+
613+
The name of the test
614+
457615
### `context.runOnly(shouldRunOnlyTests)`
458616

459617
- `shouldRunOnlyTests` {boolean} Whether or not to run `only` tests.
@@ -528,6 +686,10 @@ An instance of `SuiteContext` is passed to each suite function in order to
528686
interact with the test runner. However, the `SuiteContext` constructor is not
529687
exposed as part of the API.
530688

689+
### `context.name`
690+
691+
The name of the suite
692+
531693
### `context.signal`
532694

533695
* [`AbortSignal`][] Can be used to abort test subtasks when the test has been aborted.

lib/internal/errors.js

+15
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ const {
1616
ReflectApply,
1717
SafeMap,
1818
SafeWeakMap,
19+
StringPrototypeIncludes,
1920
StringPrototypeMatch,
2021
StringPrototypeStartsWith,
22+
StringPrototypeSlice,
2123
Symbol,
2224
SymbolFor
2325
} = require('#internal/per_context/primordials')
@@ -362,6 +364,19 @@ E('ERR_TEST_FAILURE', function (error, failureType) {
362364
E('ERR_INVALID_ARG_TYPE',
363365
(name, expected, actual) => `Expected ${name} to be ${expected}, got type ${typeof actual}`,
364366
TypeError)
367+
E('ERR_INVALID_ARG_VALUE', (name, value, reason = 'is invalid') => {
368+
let inspected
369+
try {
370+
inspected = String(value)
371+
} catch {
372+
inspected = `type ${typeof value}`
373+
}
374+
if (inspected.length > 128) {
375+
inspected = `${StringPrototypeSlice(inspected, 0, 128)}...`
376+
}
377+
const type = StringPrototypeIncludes(name, '.') ? 'property' : 'argument'
378+
return `The ${type} '${name}' ${reason}. Received ${inspected}`
379+
}, TypeError, RangeError)
365380
E('ERR_OUT_OF_RANGE',
366381
(name, expected, actual) => `Expected ${name} to be ${expected}, got ${actual}`,
367382
RangeError)

lib/internal/per_context/primordials.js

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ exports.ArrayPrototypeFilter = (arr, fn) => arr.filter(fn)
77
exports.ArrayPrototypeForEach = (arr, fn, thisArg) => arr.forEach(fn, thisArg)
88
exports.ArrayPrototypeIncludes = (arr, el, fromIndex) => arr.includes(el, fromIndex)
99
exports.ArrayPrototypeJoin = (arr, str) => arr.join(str)
10+
exports.ArrayPrototypeMap = (arr, mapFn) => arr.map(mapFn)
1011
exports.ArrayPrototypePush = (arr, ...el) => arr.push(...el)
1112
exports.ArrayPrototypeReduce = (arr, fn, originalVal) => arr.reduce(fn, originalVal)
1213
exports.ArrayPrototypeShift = arr => arr.shift()
@@ -27,6 +28,7 @@ exports.ObjectFreeze = obj => Object.freeze(obj)
2728
exports.ObjectGetOwnPropertyDescriptor = (obj, key) => Object.getOwnPropertyDescriptor(obj, key)
2829
exports.ObjectIsExtensible = obj => Object.isExtensible(obj)
2930
exports.ObjectPrototypeHasOwnProperty = (obj, property) => Object.prototype.hasOwnProperty.call(obj, property)
31+
exports.ObjectSeal = (obj) => Object.seal(obj)
3032
exports.ReflectApply = (target, self, args) => Reflect.apply(target, self, args)
3133
exports.Promise = Promise
3234
exports.PromiseAll = iterator => Promise.all(iterator)
@@ -39,11 +41,13 @@ exports.SafePromiseAll = (array, mapFn) => Promise.all(mapFn ? array.map(mapFn)
3941
exports.SafePromiseRace = (array, mapFn) => Promise.race(mapFn ? array.map(mapFn) : array)
4042
exports.SafeSet = Set
4143
exports.SafeWeakMap = WeakMap
44+
exports.StringPrototypeIncludes = (str, needle) => str.includes(needle)
4245
exports.StringPrototypeMatch = (str, reg) => str.match(reg)
4346
exports.StringPrototypeReplace = (str, search, replacement) =>
4447
str.replace(search, replacement)
4548
exports.StringPrototypeReplaceAll = replaceAll
4649
exports.StringPrototypeStartsWith = (haystack, needle, index) => haystack.startsWith(needle, index)
50+
exports.StringPrototypeSlice = (str, ...args) => str.slice(...args)
4751
exports.StringPrototypeSplit = (str, search, limit) => str.split(search, limit)
4852
exports.Symbol = Symbol
4953
exports.SymbolFor = repr => Symbol.for(repr)

lib/internal/test_runner/harness.js

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// https://github.com/nodejs/node/blob/26e27424ad91c60a44d3d4c58b62a39b555ba75d/lib/internal/test_runner/harness.js
1+
// https://github.com/nodejs/node/blob/659dc126932f986fc33c7f1c878cb2b57a1e2fac/lib/internal/test_runner/harness.js
22
'use strict'
33
const {
44
ArrayPrototypeForEach,
@@ -177,8 +177,19 @@ function runInParentContext (Factory) {
177177
return cb
178178
}
179179

180+
function hook (hook) {
181+
return (fn, options) => {
182+
const parent = testResources.get(executionAsyncId()) || setup(root)
183+
parent.createHook(hook, fn, options)
184+
}
185+
}
186+
180187
module.exports = {
181188
test: FunctionPrototypeBind(test, root),
182189
describe: runInParentContext(Suite),
183-
it: runInParentContext(ItTest)
190+
it: runInParentContext(ItTest),
191+
before: hook('before'),
192+
after: hook('after'),
193+
beforeEach: hook('beforeEach'),
194+
afterEach: hook('afterEach')
184195
}

0 commit comments

Comments
 (0)