Skip to content

Commit

Permalink
Docs: Add async hook example to QUnit.module docs
Browse files Browse the repository at this point in the history
* Re-arrange the examples a bit to prioritise what I believe we
  more commonly needed/useful examples for new code. Move down
  the "Hooks via module options" example, and update its name
  to better explain the special case of it as it is no longer the
  default way of creating hooks.

* Rename promise hooks to "Async hook callback" and let it start
  with a new async-await example.

* Use parent/child terms instead of the less clear mixed analogy
  with "parent" vs "nested".

Follows-up commit bad99f9, which
suggests use of hooks, but we did not have an example of that in the
docs.

Ref #1761.
  • Loading branch information
Krinkle committed Jun 10, 2024
1 parent bad99f9 commit 086a0db
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 70 deletions.
138 changes: 77 additions & 61 deletions docs/api/QUnit/module.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Hooks that run _before_ a test, are ordered from outer-most to inner-most, in th

#### Hook callback

A hook callback may be an async function, and may return a Promise or any other then-able. QUnit will automatically wait for your hook's asynchronous work to finish before continuing to execute the tests.
A hook callback may be an async function, and may return a Promise or any other then-able. QUnit will automatically wait for your hook's asynchronous work to finish before continuing to execute the tests. Example: [§ Async hook callback](#async-hook-callback).

Each hook has access to the same `assert` object, and test context via `this`, as the [QUnit.test](./test.md) that the hook is running for. Example: [§ Using the test context](#using-the-test-context).

Expand All @@ -73,7 +73,7 @@ You can use the options object to add [hooks](#hooks).

Properties on the module options object are copied over to the test context object at the start of each test. Such properties can also be changed from the hook callbacks. See [§ Using the test context](#using-the-test-context).

Example: [§ Declaring module options](#declaring-module-options).
Example: [§ Hooks via module options](#hooks-via-module-options).

### Nested scope

Expand Down Expand Up @@ -142,26 +142,7 @@ QUnit.test('basic test example 4', assert => {
});
```

### Declaring module options

```js
QUnit.module('module A', {
before: function () {
// prepare something once for all tests
},
beforeEach: function () {
// prepare something before each test
},
afterEach: function () {
// clean up after each test
},
after: function () {
// clean up once after all tests are done
}
});
```

### Nested module scope
<span id="nested-module-scope"></span>Nested module scope:

```js
QUnit.module('Group A', hooks => {
Expand All @@ -185,13 +166,15 @@ QUnit.module('Group B', hooks => {
});
```

### Hooks on nested modules
<span id="hooks-on-nested-modules"></span>

### Set hook callbacks

Use `before`/`beforeEach` hooks are queued for nested modules. `after`/`afterEach` hooks are stacked on nested modules.

```js
QUnit.module('My Group', hooks => {
// It is valid to call the same hook methods more than once.
// You may call hooks.beforeEach() multiple times to create multiple hooks.
hooks.beforeEach(assert => {
assert.ok(true, 'beforeEach called');
});
Expand All @@ -206,26 +189,92 @@ QUnit.module('My Group', hooks => {
assert.expect(2);
});

QUnit.module('Nested Group', hooks => {
QUnit.module('Nested Child', hooks => {
// This will run after the parent module's beforeEach hook
hooks.beforeEach(assert => {
assert.ok(true, 'nested beforeEach called');
});

// This will run before the parent module's afterEach
// This will run before the parent module's afterEach hook
hooks.afterEach(assert => {
assert.ok(true, 'nested afterEach called');
});

QUnit.test('with nested hooks', assert => {
// 2 x beforeEach (parent, current)
// 2 x afterEach (current, parent)
// 2 x beforeEach (parent, child)
// 2 x afterEach (child, parent)
assert.expect(4);
});
});
});
```

### Async hook callback

```js
QUnit.module('Database connection', function (hooks) {
hooks.before(async function () {
await MyDb.connect();
});

hooks.after(async function () {
await MyDb.disconnect();
});
});
```

<span id="module-hook-with-promise"></span>Module hook with Promise:

An example of handling an asynchronous `then`able Promise result in hooks. This example uses an [ES6 Promise][] interface that is fulfilled after connecting to or disconnecting from database.

[ES6 Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

```js
QUnit.module('Database connection', {
before: function () {
return new Promise(function (resolve, reject) {
MyDb.connect(function (err) {
if (err) {
reject(err);
} else {
resolve();
}
});
});
},
after: function () {
return new Promise(function (resolve, reject) {
MyDb.disconnect(function (err) {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
});
```

### Hooks via module options

```js
QUnit.module('module A', {
before: function () {
// prepare something once for all tests
},
beforeEach: function () {
// prepare something before each test
},
afterEach: function () {
// clean up after each test
},
after: function () {
// clean up once after all tests are done
}
});
```

### Using the test context

The test context object is exposed to hook callbacks.
Expand Down Expand Up @@ -300,39 +349,6 @@ QUnit.module('Machine Maker', hooks => {
});
```

### Module hook with Promise

An example of handling an asynchronous `then`able Promise result in hooks. This example uses an [ES6 Promise][] interface that is fulfilled after connecting to or disconnecting from database.

[ES6 Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

```js
QUnit.module('Database connection', {
before: function () {
return new Promise(function (resolve, reject) {
DB.connect(function (err) {
if (err) {
reject(err);
} else {
resolve();
}
});
});
},
after: function () {
return new Promise(function (resolve, reject) {
DB.disconnect(function (err) {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
});
```

### Only run a subset of tests

Use `QUnit.module.only()` to treat an entire module's tests as if they used [`QUnit.test.only`](./test.only.md) instead of [`QUnit.test`](./test.md).
Expand Down
2 changes: 1 addition & 1 deletion test/cli/fixtures/async-module-error-promise.tap.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ ok 2 module manually returning a promise > has a test
# todo 0
# fail 1

# exit code: 1
# exit code: 1
2 changes: 1 addition & 1 deletion test/cli/fixtures/async-module-error-thenable.tap.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ ok 2 module manually returning a thenable > has a test
# todo 0
# fail 1

# exit code: 1
# exit code: 1
2 changes: 1 addition & 1 deletion test/cli/fixtures/async-module-error.tap.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ not ok 2 global failure
# todo 0
# fail 2

# exit code: 1
# exit code: 1
12 changes: 6 additions & 6 deletions test/main/modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -455,19 +455,19 @@ QUnit.module('QUnit.module', function () {
});
});

QUnit.module('disallowed async module callbacks', function () {
var errorFromThenableCallbackModule;
QUnit.module('disallow async module callback', function () {
var caught;
try {
QUnit.module('with thenable callback', function () {
return { then: function () {} };
});
} catch (e) {
errorFromThenableCallbackModule = e;
caught = e;
}

QUnit.test('module with thenable callback function errored', function (assert) {
assert.true(errorFromThenableCallbackModule instanceof Error);
assert.strictEqual(errorFromThenableCallbackModule.message, 'Returning a promise from a module callback is not supported. Instead, use hooks for async behavior.');
QUnit.test('thenable callback function errored', function (assert) {
assert.true(caught instanceof Error);
assert.strictEqual(caught.message, 'Returning a promise from a module callback is not supported. Instead, use hooks for async behavior.');
});
});
});

0 comments on commit 086a0db

Please sign in to comment.