Skip to content

Commit

Permalink
test_runner: add before/after/each hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
MoLow committed Jul 24, 2022
1 parent 7ef069e commit 645c2da
Show file tree
Hide file tree
Showing 9 changed files with 632 additions and 39 deletions.
195 changes: 195 additions & 0 deletions doc/api/test.md
Expand Up @@ -438,6 +438,120 @@ same as [`it([name], { skip: true }[, fn])`][it options].
Shorthand for marking a test as `TODO`,
same as [`it([name], { todo: true }[, fn])`][it options].

### `before([, fn][, options])`

<!-- YAML
added: REPLACEME
-->

* `fn` {Function|AsyncFunction} The hook function.
If the hook uses callbacks,
the callback function is passed as the second argument. **Default:** A no-op
function.
* `options` {Object} Configuration options for the hook. The following
properties are supported:
* `signal` {AbortSignal} Allows aborting an in-progress hook
* `timeout` {number} A number of milliseconds the hook will fail after.
If unspecified, subtests inherit this value from their parent.
**Default:** `Infinity`.

This function is used to create a hook running before running a suite.

```js
describe('tests', async () => {
before(() => console.log('about to run some test'));
it('is a subtest', () => {
assert.ok('some relevant assertion here');
});
});
```

### `after([, fn][, options])`

<!-- YAML
added: REPLACEME
-->

* `fn` {Function|AsyncFunction} The hook function.
If the hook uses callbacks,
the callback function is passed as the second argument. **Default:** A no-op
function.
* `options` {Object} Configuration options for the hook. The following
properties are supported:
* `signal` {AbortSignal} Allows aborting an in-progress hook
* `timeout` {number} A number of milliseconds the hook will fail after.
If unspecified, subtests inherit this value from their parent.
**Default:** `Infinity`.

This function is used to create a hook running after running a suite.

```js
describe('tests', async () => {
after(() => console.log('finished running tests'));
it('is a subtest', () => {
assert.ok('some relevant assertion here');
});
});
```

### `beforeEach([, fn][, options])`

<!-- YAML
added: REPLACEME
-->

* `fn` {Function|AsyncFunction} The hook function.
If the hook uses callbacks,
the callback function is passed as the second argument. **Default:** A no-op
function.
* `options` {Object} Configuration options for the hook. The following
properties are supported:
* `signal` {AbortSignal} Allows aborting an in-progress hook
* `timeout` {number} A number of milliseconds the hook will fail after.
If unspecified, subtests inherit this value from their parent.
**Default:** `Infinity`.

This function is used to create a hook running
before each subtest of the current suite.

```js
describe('tests', async () => {
beforeEach(() => t.diagnostics('about to run a test'));
it('is a subtest', () => {
assert.ok('some relevant assertion here');
});
});
```

### `afterEach([, fn][, options])`

<!-- YAML
added: REPLACEME
-->

* `fn` {Function|AsyncFunction} The hook function.
If the hook uses callbacks,
the callback function is passed as the second argument. **Default:** A no-op
function.
* `options` {Object} Configuration options for the hook. The following
properties are supported:
* `signal` {AbortSignal} Allows aborting an in-progress hook
* `timeout` {number} A number of milliseconds the hook will fail after.
If unspecified, subtests inherit this value from their parent.
**Default:** `Infinity`.

This function is used to create a hook running
after each subtest of the current test.

```js
describe('tests', async () => {
afterEach(() => t.diagnostics('about to run a test'));
it('is a subtest', () => {
assert.ok('some relevant assertion here');
});
});
```

## Class: `TestContext`

<!-- YAML
Expand All @@ -448,6 +562,70 @@ An instance of `TestContext` is passed to each test function in order to
interact with the test runner. However, the `TestContext` constructor is not
exposed as part of the API.

### `context.beforeEach([, fn][, options])`

<!-- YAML
added: REPLACEME
-->

* `fn` {Function|AsyncFunction} The hook function. The first argument
to this function is a [`TestContext`][] object. If the hook uses callbacks,
the callback function is passed as the second argument. **Default:** A no-op
function.
* `options` {Object} Configuration options for the hook. The following
properties are supported:
* `signal` {AbortSignal} Allows aborting an in-progress hook
* `timeout` {number} A number of milliseconds the hook will fail after.
If unspecified, subtests inherit this value from their parent.
**Default:** `Infinity`.

This function is used to create a hook running
before each subtest of the current test.

```js
test('top level test', async (t) => {
t.beforeEach((t) => t.diagnostics(`about to run ${t.name}`));
await t.test(
'This is a subtest',
(t) => {
assert.ok('some relevant assertion here');
}
);
});
```

### `context.afterEach([, fn][, options])`

<!-- YAML
added: REPLACEME
-->

* `fn` {Function|AsyncFunction} The hook function. The first argument
to this function is a [`TestContext`][] object. If the hook uses callbacks,
the callback function is passed as the second argument. **Default:** A no-op
function.
* `options` {Object} Configuration options for the hook. The following
properties are supported:
* `signal` {AbortSignal} Allows aborting an in-progress hook
* `timeout` {number} A number of milliseconds the hook will fail after.
If unspecified, subtests inherit this value from their parent.
**Default:** `Infinity`.

This function is used to create a hook running
after each subtest of the current test.

```js
test('top level test', async (t) => {
t.afterEach((t) => t.diagnostics(`finished running ${t.name}`));
await t.test(
'This is a subtest',
(t) => {
assert.ok('some relevant assertion here');
}
);
});
```

### `context.diagnostic(message)`

<!-- YAML
Expand All @@ -466,6 +644,14 @@ test('top level test', (t) => {
});
```

### `context.name`

<!-- YAML
added: REPLACEME
-->

The name of the test

### `context.runOnly(shouldRunOnlyTests)`

<!-- YAML
Expand Down Expand Up @@ -564,6 +750,7 @@ changes:
* `only` {boolean} If truthy, and the test context is configured to run
`only` tests, then this test will be run. Otherwise, the test is skipped.
**Default:** `false`.
* `signal` {AbortSignal} Allows aborting an in-progress test
* `skip` {boolean|string} If truthy, the test is skipped. If a string is
provided, that string is displayed in the test results as the reason for
skipping the test. **Default:** `false`.
Expand Down Expand Up @@ -604,6 +791,14 @@ An instance of `SuiteContext` is passed to each suite function in order to
interact with the test runner. However, the `SuiteContext` constructor is not
exposed as part of the API.

### `context.name`

<!-- YAML
added: REPLACEME
-->

The name of the suite

### `context.signal`

<!-- YAML
Expand Down
11 changes: 11 additions & 0 deletions lib/internal/test_runner/harness.js
Expand Up @@ -170,8 +170,19 @@ function runInParentContext(Factory) {
return cb;
}

function hook(hook) {
return (fn, options) => {
const parent = testResources.get(executionAsyncId()) || setup(root);
parent.createHook(hook, fn, options);
};
}

module.exports = {
test: FunctionPrototypeBind(test, root),
describe: runInParentContext(Suite),
it: runInParentContext(ItTest),
before: hook('before'),
after: hook('after'),
beforeEach: hook('beforeEach'),
afterEach: hook('afterEach'),
};

0 comments on commit 645c2da

Please sign in to comment.