Skip to content

Commit e29cd3f

Browse files
MoLowaduh95
authored andcommitted
feat: expose describe and it
PR-URL: nodejs/node#43420 Refs: nodejs/node#43415 Reviewed-By: Benjamin Gruenbaum <[email protected]>
1 parent fc0256b commit e29cd3f

File tree

9 files changed

+1086
-71
lines changed

9 files changed

+1086
-71
lines changed

Diff for: README.md

+93-2
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,42 @@ test('skip() method with message', t => {
152152
})
153153
```
154154

155+
## `describe`/`it` syntax
156+
157+
Running tests can also be done using `describe` to declare a suite
158+
and `it` to declare a test.
159+
A suite is used to organize and group related tests together.
160+
`it` is an alias for `test`, except there is no test context passed,
161+
since nesting is done using suites, as demonstrated in this example
162+
163+
```js
164+
describe('A thing', () => {
165+
it('should work', () => {
166+
assert.strictEqual(1, 1);
167+
});
168+
169+
it('should be ok', () => {
170+
assert.strictEqual(2, 2);
171+
});
172+
173+
describe('a nested thing', () => {
174+
it('should work', () => {
175+
assert.strictEqual(3, 3);
176+
});
177+
});
178+
});
179+
```
180+
181+
`describe` and `it` are imported from the `test` module
182+
183+
```mjs
184+
import { describe, it } from 'test';
185+
```
186+
187+
```cjs
188+
const { describe, it } = require('test');
189+
```
190+
155191
### `only` tests
156192

157193
If `node--test` is started with the `--test-only` command-line option, it is
@@ -303,7 +339,7 @@ internally.
303339
- `todo` {boolean|string} If truthy, the test marked as `TODO`. If a string
304340
is provided, that string is displayed in the test results as the reason why
305341
the test is `TODO`. **Default:** `false`.
306-
- `fn` {Function|AsyncFunction} The function under test. This first argument
342+
- `fn` {Function|AsyncFunction} The function under test. The first argument
307343
to this function is a [`TestContext`][] object. If the test uses callbacks,
308344
the callback function is passed as the second argument. **Default:** A no-op
309345
function.
@@ -335,6 +371,59 @@ test('top level test', async t => {
335371
})
336372
```
337373

374+
## `describe([name][, options][, fn])`
375+
376+
* `name` {string} The name of the suite, which is displayed when reporting test
377+
results. **Default:** The `name` property of `fn`, or `'<anonymous>'` if `fn`
378+
does not have a name.
379+
* `options` {Object} Configuration options for the suite.
380+
supports the same options as `test([name][, options][, fn])`
381+
* `fn` {Function} The function under suite.
382+
a synchronous function declaring all subtests and subsuites.
383+
**Default:** A no-op function.
384+
* Returns: `undefined`.
385+
386+
The `describe()` function imported from the `test` module. Each
387+
invocation of this function results in the creation of a Subtest
388+
and a test point in the TAP output.
389+
After invocation of top level `describe` functions,
390+
all top level tests and suites will execute
391+
392+
## `describe.skip([name][, options][, fn])`
393+
394+
Shorthand for skipping a suite, same as [`describe([name], { skip: true }[, fn])`][describe options].
395+
396+
## `describe.todo([name][, options][, fn])`
397+
398+
Shorthand for marking a suite as `TODO`, same as
399+
[`describe([name], { todo: true }[, fn])`][describe options].
400+
401+
## `it([name][, options][, fn])`
402+
403+
* `name` {string} The name of the test, which is displayed when reporting test
404+
results. **Default:** The `name` property of `fn`, or `'<anonymous>'` if `fn`
405+
does not have a name.
406+
* `options` {Object} Configuration options for the suite.
407+
supports the same options as `test([name][, options][, fn])`.
408+
* `fn` {Function|AsyncFunction} The function under test.
409+
If the test uses callbacks, the callback function is passed as an argument.
410+
**Default:** A no-op function.
411+
* Returns: `undefined`.
412+
413+
The `it()` function is the value imported from the `test` module.
414+
Each invocation of this function results in the creation of a test point in the
415+
TAP output.
416+
417+
## `it.skip([name][, options][, fn])`
418+
419+
Shorthand for skipping a test,
420+
same as [`it([name], { skip: true }[, fn])`][it options].
421+
422+
## `it.todo([name][, options][, fn])`
423+
424+
Shorthand for marking a test as `TODO`,
425+
same as [`it([name], { todo: true }[, fn])`][it options].
426+
338427
## Class: `TestContext`
339428

340429
An instance of `TestContext` is passed to each test function in order to
@@ -394,7 +483,7 @@ execution of the test function. This function does not return a value.
394483
- `todo` {boolean|string} If truthy, the test marked as `TODO`. If a string
395484
is provided, that string is displayed in the test results as the reason why
396485
the test is `TODO`. **Default:** `false`.
397-
- `fn` {Function|AsyncFunction} The function under test. This first argument
486+
- `fn` {Function|AsyncFunction} The function under test. The first argument
398487
to this function is a [`TestContext`][] object. If the test uses callbacks,
399488
the callback function is passed as the second argument. **Default:** A no-op
400489
function.
@@ -406,6 +495,8 @@ behaves in the same fashion as the top level [`test()`][] function.
406495
[tap]: https://testanything.org/
407496
[`testcontext`]: #class-testcontext
408497
[`test()`]: #testname-options-fn
498+
[describe options]: #describename-options-fn
499+
[it options]: #testname-options-fn
409500
[test runner execution model]: #test-runner-execution-model
410501

411502
## License

Diff for: lib/internal/main/test_runner.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// https://github.com/nodejs/node/blob/1aab13cad9c800f4121c1d35b554b78c1b17bdbd/lib/internal/main/test_runner.js
1+
// https://github.com/nodejs/node/blob/e2225ba8e1c00995c0f8bd56e607ea7c5b463ab9/lib/internal/main/test_runner.js
22
'use strict'
33
const {
44
ArrayFrom,
@@ -21,7 +21,7 @@ const {
2121
ERR_TEST_FAILURE
2222
}
2323
} = require('#internal/errors')
24-
const test = require('#internal/test_runner/harness')
24+
const { test } = require('#internal/test_runner/harness')
2525
const { kSubtestsFailed } = require('#internal/test_runner/test')
2626
const {
2727
isSupportedFileType,

Diff for: lib/internal/per_context/primordials.js

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ 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)
1010
exports.ArrayPrototypePush = (arr, ...el) => arr.push(...el)
11+
exports.ArrayPrototypeReduce = (arr, fn, originalVal) => arr.reduce(fn, originalVal)
1112
exports.ArrayPrototypeShift = arr => arr.shift()
1213
exports.ArrayPrototypeSlice = (arr, offset) => arr.slice(offset)
1314
exports.ArrayPrototypeSort = (arr, fn) => arr.sort(fn)
@@ -27,6 +28,7 @@ exports.ObjectIsExtensible = obj => Object.isExtensible(obj)
2728
exports.ObjectPrototypeHasOwnProperty = (obj, property) => Object.prototype.hasOwnProperty.call(obj, property)
2829
exports.ReflectApply = (target, self, args) => Reflect.apply(target, self, args)
2930
exports.Promise = Promise
31+
exports.PromiseResolve = val => Promise.resolve(val)
3032
exports.SafeMap = Map
3133
exports.SafeSet = Set
3234
exports.SafeWeakMap = WeakMap

Diff for: lib/internal/test_runner/harness.js

+62-30
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
// https://github.com/nodejs/node/blob/1aab13cad9c800f4121c1d35b554b78c1b17bdbd/lib/internal/test_runner/harness.js
2-
1+
// https://github.com/nodejs/node/blob/e2225ba8e1c00995c0f8bd56e607ea7c5b463ab9/lib/internal/test_runner/harness.js
32
'use strict'
4-
5-
const { FunctionPrototypeBind, SafeMap } = require('#internal/per_context/primordials')
3+
const {
4+
ArrayPrototypeForEach,
5+
FunctionPrototypeBind,
6+
SafeMap
7+
} = require('#internal/per_context/primordials')
68
const {
79
createHook,
810
executionAsyncId
@@ -12,34 +14,44 @@ const {
1214
ERR_TEST_FAILURE
1315
}
1416
} = require('#internal/errors')
15-
const { Test } = require('#internal/test_runner/test')
17+
const { Test, ItTest, Suite } = require('#internal/test_runner/test')
1618

17-
function createProcessEventHandler (eventName, rootTest, testResources) {
19+
const testResources = new SafeMap()
20+
const root = new Test({ __proto__: null, name: '<root>' })
21+
let wasRootSetup = false
22+
23+
function createProcessEventHandler (eventName, rootTest) {
1824
return (err) => {
1925
// Check if this error is coming from a test. If it is, fail the test.
2026
const test = testResources.get(executionAsyncId())
2127

22-
if (test !== undefined) {
23-
if (test.finished) {
24-
// If the test is already finished, report this as a top level
25-
// diagnostic since this is a malformed test.
26-
const msg = `Warning: Test "${test.name}" generated asynchronous ` +
27-
'activity after the test ended. This activity created the error ' +
28-
`"${err}" and would have caused the test to fail, but instead ` +
29-
`triggered an ${eventName} event.`
28+
if (!test) {
29+
// Node.js 14.x crashes if the error is throw here.
30+
if (process.version.startsWith('v14.')) return
31+
throw err
32+
}
3033

31-
rootTest.diagnostic(msg)
32-
return
33-
}
34+
if (test.finished) {
35+
// If the test is already finished, report this as a top level
36+
// diagnostic since this is a malformed test.
37+
const msg = `Warning: Test "${test.name}" generated asynchronous ` +
38+
'activity after the test ended. This activity created the error ' +
39+
`"${err}" and would have caused the test to fail, but instead ` +
40+
`triggered an ${eventName} event.`
3441

35-
test.fail(new ERR_TEST_FAILURE(err, eventName))
36-
test.postRun()
42+
rootTest.diagnostic(msg)
43+
return
3744
}
45+
46+
test.fail(new ERR_TEST_FAILURE(err, eventName))
47+
test.postRun()
3848
}
3949
}
4050

4151
function setup (root) {
42-
const testResources = new SafeMap()
52+
if (wasRootSetup) {
53+
return root
54+
}
4355
const hook = createHook({
4456
init (asyncId, type, triggerAsyncId, resource) {
4557
if (resource instanceof Test) {
@@ -61,9 +73,9 @@ function setup (root) {
6173
hook.enable()
6274

6375
const exceptionHandler =
64-
createProcessEventHandler('uncaughtException', root, testResources)
76+
createProcessEventHandler('uncaughtException', root)
6577
const rejectionHandler =
66-
createProcessEventHandler('unhandledRejection', root, testResources)
78+
createProcessEventHandler('unhandledRejection', root)
6779

6880
process.on('uncaughtException', exceptionHandler)
6981
process.on('unhandledRejection', rejectionHandler)
@@ -116,19 +128,39 @@ function setup (root) {
116128

117129
root.reporter.pipe(process.stdout)
118130
root.reporter.version()
131+
132+
wasRootSetup = true
133+
return root
119134
}
120135

121136
function test (name, options, fn) {
122-
// If this is the first test encountered, bootstrap the test harness.
123-
if (this.subtests.length === 0) {
124-
setup(this)
137+
const subtest = setup(root).createSubtest(Test, name, options, fn)
138+
return subtest.start()
139+
}
140+
141+
function runInParentContext (Factory) {
142+
function run (name, options, fn, overrides) {
143+
const parent = testResources.get(executionAsyncId()) || setup(root)
144+
const subtest = parent.createSubtest(Factory, name, options, fn, overrides)
145+
if (parent === root) {
146+
subtest.start()
147+
}
125148
}
126149

127-
const subtest = this.createSubtest(name, options, fn)
150+
const cb = (name, options, fn) => {
151+
run(name, options, fn)
152+
}
128153

129-
return subtest.start()
154+
ArrayPrototypeForEach(['skip', 'todo'], (keyword) => {
155+
cb[keyword] = (name, options, fn) => {
156+
run(name, options, fn, { [keyword]: true })
157+
}
158+
})
159+
return cb
130160
}
131161

132-
const root = new Test({ name: '<root>' })
133-
134-
module.exports = FunctionPrototypeBind(test, root)
162+
module.exports = {
163+
test: FunctionPrototypeBind(test, root),
164+
describe: runInParentContext(Suite),
165+
it: runInParentContext(ItTest)
166+
}

0 commit comments

Comments
 (0)