Skip to content

Commit 1dcfe60

Browse files
committed
test_runner: before and after each hooks
1 parent a933a75 commit 1dcfe60

File tree

8 files changed

+223
-53
lines changed

8 files changed

+223
-53
lines changed

lib/internal/test_runner/harness.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,20 @@ function runInParentContext(Factory) {
170170
return cb;
171171
}
172172

173+
function afterEach(fn, options) {
174+
const parent = testResources.get(executionAsyncId()) || setup(root);
175+
parent.createBeforeEachHook(fn, options);
176+
177+
}
178+
function beforeEach(fn, options) {
179+
const parent = testResources.get(executionAsyncId()) || setup(root);
180+
parent.createBeforeEachHook(fn, options);
181+
}
182+
173183
module.exports = {
174184
test: FunctionPrototypeBind(test, root),
175185
describe: runInParentContext(Suite),
176186
it: runInParentContext(ItTest),
187+
afterEach,
188+
beforeEach,
177189
};

lib/internal/test_runner/test.js

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ class TestContext {
4747
this.#test = test;
4848
}
4949

50+
get name() {
51+
return this.#test.name;
52+
}
53+
5054
diagnostic(message) {
5155
this.#test.diagnostic(message);
5256
}
@@ -69,6 +73,14 @@ class TestContext {
6973

7074
return subtest.start();
7175
}
76+
77+
beforeEach(fn, options) {
78+
this.#test.createBeforeEachHook(fn, options);
79+
}
80+
81+
afterEach(fn, options) {
82+
this.#test.createAfterEachHook(fn, options);
83+
}
7284
}
7385

7486
class Test extends AsyncResource {
@@ -140,6 +152,8 @@ class Test extends AsyncResource {
140152
this.pendingSubtests = [];
141153
this.readySubtests = new SafeMap();
142154
this.subtests = [];
155+
this.beforeEachHooks = [];
156+
this.afterEachHooks = [];
143157
this.waitingOn = 0;
144158
this.finished = false;
145159
}
@@ -242,6 +256,18 @@ class Test extends AsyncResource {
242256
return test;
243257
}
244258

259+
createBeforeEachHook(fn, options) {
260+
const hook = new TestHook(fn, options);
261+
ArrayPrototypePush(this.beforeEachHooks, hook);
262+
return hook;
263+
}
264+
265+
createAfterEachHook(fn, options) {
266+
const hook = new TestHook(fn, options);
267+
ArrayPrototypePush(this.afterEachHooks, hook);
268+
return hook;
269+
}
270+
245271
cancel() {
246272
if (this.endTime !== null) {
247273
return;
@@ -253,6 +279,7 @@ class Test extends AsyncResource {
253279
kCancelledByParent
254280
)
255281
);
282+
this.startTime = this.startTime || this.endTime; // if a test was canceled before it was started, e.g inside a hook
256283
this.cancelled = true;
257284
}
258285

@@ -309,12 +336,24 @@ class Test extends AsyncResource {
309336
return { ctx, args: [ctx] };
310337
}
311338

312-
async run() {
313-
this.parent.activeSubtests++;
339+
async #runHooks(hooks) {
340+
await ArrayPrototypeReduce(hooks, async (prev, hook) => {
341+
await prev;
342+
await hook.run(this.getRunArgs());
343+
}, PromiseResolve());
344+
}
345+
346+
async run(...runArgs) {
347+
if (this.parent !== null) {
348+
this.parent.activeSubtests++;
349+
}
350+
if (this.parent?.beforeEachHooks.length > 0) {
351+
await this.#runHooks(this.parent.beforeEachHooks);
352+
}
314353
this.startTime = hrtime();
315354

316355
try {
317-
const { args, ctx } = this.getRunArgs();
356+
const { args, ctx } = ReflectApply(this.getRunArgs, this, runArgs);
318357
ArrayPrototypeUnshift(args, this.fn, ctx); // Note that if it's not OK to mutate args, we need to first clone it.
319358

320359
if (this.fn.length === args.length - 1) {
@@ -347,9 +386,13 @@ class Test extends AsyncResource {
347386
}
348387
}
349388

389+
if (this.parent?.afterEachHooks.length > 0) {
390+
await this.#runHooks(this.parent.afterEachHooks);
391+
}
392+
350393
// Clean up the test. Then, try to report the results and execute any
351394
// tests that were pending due to available concurrency.
352-
this.postRun();
395+
await this.postRun();
353396
}
354397

355398
postRun() {
@@ -387,7 +430,7 @@ class Test extends AsyncResource {
387430
this.parent.activeSubtests--;
388431
this.parent.addReadySubtest(this);
389432
this.parent.processReadySubtestRange(false);
390-
this.parent.processPendingSubtests();
433+
return this.parent.processPendingSubtests();
391434
}
392435
}
393436

@@ -447,10 +490,23 @@ class Test extends AsyncResource {
447490
}
448491
}
449492

493+
class TestHook extends Test {
494+
constructor(fn, options) {
495+
if (options === null || typeof options !== 'object') {
496+
options = kEmptyObject;
497+
}
498+
super({ fn, ...options });
499+
}
500+
getRunArgs(testContext) {
501+
return testContext;
502+
}
503+
}
504+
450505
class ItTest extends Test {
451506
constructor(opt) { super(opt); } // eslint-disable-line no-useless-constructor
452507
getRunArgs() {
453-
return { ctx: {}, args: [] };
508+
const ctx = new TestContext(this);
509+
return { ctx, args: [] };
454510
}
455511
}
456512
class Suite extends Test {

lib/test.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
'use strict';
2-
const { test, describe, it } = require('internal/test_runner/harness');
2+
const { test, describe, it, afterEach, beforeEach } = require('internal/test_runner/harness');
33
const { emitExperimentalWarning } = require('internal/util');
44

55
emitExperimentalWarning('The test runner');
@@ -8,3 +8,5 @@ module.exports = test;
88
module.exports.test = test;
99
module.exports.describe = describe;
1010
module.exports.it = it;
11+
module.exports.afterEach = afterEach;
12+
module.exports.beforeEach = beforeEach;

test/message/test_runner_desctibe_it.js

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -219,19 +219,6 @@ it('callback fail', (done) => {
219219
});
220220
});
221221

222-
it('sync t is this in test', function() {
223-
assert.deepStrictEqual(this, {});
224-
});
225-
226-
it('async t is this in test', async function() {
227-
assert.deepStrictEqual(this, {});
228-
});
229-
230-
it('callback t is this in test', function(done) {
231-
assert.deepStrictEqual(this, {});
232-
done();
233-
});
234-
235222
it('callback also returns a Promise', async (done) => {
236223
throw new Error('thrown from callback also returns a Promise');
237224
});

test/message/test_runner_desctibe_it.out

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ not ok 8 - sync throw fail
7474
*
7575
*
7676
*
77+
*
78+
*
79+
*
7780
...
7881
# Subtest: async skip pass
7982
ok 9 - async skip pass # SKIP
@@ -100,6 +103,9 @@ not ok 11 - async throw fail
100103
*
101104
*
102105
*
106+
*
107+
*
108+
*
103109
...
104110
# Subtest: async assertion fail
105111
not ok 12 - async assertion fail
@@ -108,9 +114,9 @@ not ok 12 - async assertion fail
108114
failureType: 'testCodeFailure'
109115
error: |-
110116
Expected values to be strictly equal:
111-
117+
112118
true !== false
113-
119+
114120
code: 'ERR_ASSERTION'
115121
stack: |-
116122
*
@@ -120,6 +126,9 @@ not ok 12 - async assertion fail
120126
*
121127
*
122128
*
129+
*
130+
*
131+
*
123132
...
124133
# Subtest: resolve pass
125134
ok 13 - resolve pass
@@ -141,6 +150,9 @@ not ok 14 - reject fail
141150
*
142151
*
143152
*
153+
*
154+
*
155+
*
144156
...
145157
# Subtest: unhandled rejection - passes but warns
146158
ok 15 - unhandled rejection - passes but warns
@@ -277,6 +289,9 @@ not ok 27 - sync skip option is false fail
277289
*
278290
*
279291
*
292+
*
293+
*
294+
*
280295
...
281296
# Subtest: <anonymous>
282297
ok 28 - <anonymous>
@@ -349,31 +364,16 @@ not ok 40 - callback fail
349364
*
350365
*
351366
...
352-
# Subtest: sync t is this in test
353-
ok 41 - sync t is this in test
354-
---
355-
duration_ms: *
356-
...
357-
# Subtest: async t is this in test
358-
ok 42 - async t is this in test
359-
---
360-
duration_ms: *
361-
...
362-
# Subtest: callback t is this in test
363-
ok 43 - callback t is this in test
364-
---
365-
duration_ms: *
366-
...
367367
# Subtest: callback also returns a Promise
368-
not ok 44 - callback also returns a Promise
368+
not ok 41 - callback also returns a Promise
369369
---
370370
duration_ms: *
371371
failureType: 'callbackAndPromisePresent'
372372
error: 'passed a callback but also returned a Promise'
373373
code: 'ERR_TEST_FAILURE'
374374
...
375375
# Subtest: callback throw
376-
not ok 45 - callback throw
376+
not ok 42 - callback throw
377377
---
378378
duration_ms: *
379379
failureType: 'testCodeFailure'
@@ -387,9 +387,12 @@ not ok 45 - callback throw
387387
*
388388
*
389389
*
390+
*
391+
*
392+
*
390393
...
391394
# Subtest: callback called twice
392-
not ok 46 - callback called twice
395+
not ok 43 - callback called twice
393396
---
394397
duration_ms: *
395398
failureType: 'multipleCallbackInvocations'
@@ -400,12 +403,12 @@ not ok 46 - callback called twice
400403
*
401404
...
402405
# Subtest: callback called twice in different ticks
403-
ok 47 - callback called twice in different ticks
406+
ok 44 - callback called twice in different ticks
404407
---
405408
duration_ms: *
406409
...
407410
# Subtest: callback called twice in future tick
408-
not ok 48 - callback called twice in future tick
411+
not ok 45 - callback called twice in future tick
409412
---
410413
duration_ms: *
411414
failureType: 'uncaughtException'
@@ -415,7 +418,7 @@ not ok 48 - callback called twice in future tick
415418
*
416419
...
417420
# Subtest: callback async throw
418-
not ok 49 - callback async throw
421+
not ok 46 - callback async throw
419422
---
420423
duration_ms: *
421424
failureType: 'uncaughtException'
@@ -425,20 +428,20 @@ not ok 49 - callback async throw
425428
*
426429
...
427430
# Subtest: callback async throw after done
428-
ok 50 - callback async throw after done
431+
ok 47 - callback async throw after done
429432
---
430433
duration_ms: *
431434
...
432435
# Subtest: custom inspect symbol fail
433-
not ok 51 - custom inspect symbol fail
436+
not ok 48 - custom inspect symbol fail
434437
---
435438
duration_ms: *
436439
failureType: 'testCodeFailure'
437440
error: 'customized'
438441
code: 'ERR_TEST_FAILURE'
439442
...
440443
# Subtest: custom inspect symbol that throws fail
441-
not ok 52 - custom inspect symbol that throws fail
444+
not ok 49 - custom inspect symbol that throws fail
442445
---
443446
duration_ms: *
444447
failureType: 'testCodeFailure'
@@ -482,15 +485,15 @@ not ok 52 - custom inspect symbol that throws fail
482485
*
483486
...
484487
1..2
485-
not ok 53 - subtest sync throw fails
488+
not ok 50 - subtest sync throw fails
486489
---
487490
duration_ms: *
488491
failureType: 'subtestsFailed'
489492
error: '2 subtests failed'
490493
code: 'ERR_TEST_FAILURE'
491494
...
492495
# Subtest: invalid subtest fail
493-
not ok 54 - invalid subtest fail
496+
not ok 51 - invalid subtest fail
494497
---
495498
duration_ms: *
496499
failureType: 'parentAlreadyFinished'
@@ -499,15 +502,15 @@ not ok 54 - invalid subtest fail
499502
stack: |-
500503
*
501504
...
502-
1..54
505+
1..51
503506
# Warning: Test "unhandled rejection - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: rejected from unhandled rejection fail" and would have caused the test to fail, but instead triggered an unhandledRejection event.
504507
# Warning: Test "async unhandled rejection - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: rejected from async unhandled rejection fail" and would have caused the test to fail, but instead triggered an unhandledRejection event.
505508
# Warning: Test "immediate throw - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: thrown from immediate throw fail" and would have caused the test to fail, but instead triggered an uncaughtException event.
506509
# Warning: Test "immediate reject - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: rejected from immediate reject fail" and would have caused the test to fail, but instead triggered an unhandledRejection event.
507510
# Warning: Test "callback called twice in different ticks" generated asynchronous activity after the test ended. This activity created the error "Error [ERR_TEST_FAILURE]: callback invoked multiple times" and would have caused the test to fail, but instead triggered an uncaughtException event.
508511
# Warning: Test "callback async throw after done" generated asynchronous activity after the test ended. This activity created the error "Error: thrown from callback async throw after done" and would have caused the test to fail, but instead triggered an uncaughtException event.
509-
# tests 54
510-
# pass 23
512+
# tests 51
513+
# pass 20
511514
# fail 17
512515
# cancelled 0
513516
# skipped 9

0 commit comments

Comments
 (0)