diff --git a/doc/api/cli.md b/doc/api/cli.md index 5baf534af1e5c8..cab061c1bf63a3 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -1086,12 +1086,29 @@ The value given must be a power of two. ### `--test` Starts the Node.js command line test runner. This flag cannot be combined with -`--check`, `--eval`, `--interactive`, or the inspector. See the documentation -on [running tests from the command line][] for more details. +`--watch-path`, `--check`, `--eval`, `--interactive`, or the inspector. +See the documentation on [running tests from the command line][] +for more details. + +### `--test-name-pattern` + + + +A regular expression that configures the test runner to only execute tests +whose name matches the provided pattern. See the documentation on +[filtering tests by name][] for more details. ### `--test-only` @@ -1430,7 +1447,11 @@ will be chosen. ### `--watch` > Stability: 1 - Experimental @@ -1464,7 +1485,7 @@ This will turn off watching of required or imported modules, even when used in combination with `--watch`. This flag cannot be combined with -`--check`, `--eval`, `--interactive`, or the REPL. +`--check`, `--eval`, `--interactive`, `--test`, or the REPL. ```console $ node --watch-path=./src --watch-path=./tests index.js @@ -2153,6 +2174,7 @@ done [debugger]: debugger.md [debugging security implications]: https://nodejs.org/en/docs/guides/debugging-getting-started/#security-implications [emit_warning]: process.md#processemitwarningwarning-options +[filtering tests by name]: test.md#filtering-tests-by-name [jitless]: https://v8.dev/blog/jitless [libuv threadpool documentation]: https://docs.libuv.org/en/latest/threadpool.html [remote code execution]: https://www.owasp.org/index.php/Code_Injection diff --git a/doc/api/errors.md b/doc/api/errors.md index a4f6902be32d63..75570302f58828 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -2648,6 +2648,25 @@ An unspecified or non-specific system error has occurred within the Node.js process. The error object will have an `err.info` object property with additional details. + + +### `ERR_TAP_LEXER_ERROR` + +An error representing a failing lexer state. + + + +### `ERR_TAP_PARSER_ERROR` + +An error representing a failing parser state. Additional information about +the token causing the error is available via the `cause` property. + + + +### `ERR_TAP_VALIDATION_ERROR` + +This error represents a failed TAP validation. + ### `ERR_TEST_FAILURE` diff --git a/doc/api/test.md b/doc/api/test.md index 7da51afff4b7f9..c5f049cf43313e 100644 --- a/doc/api/test.md +++ b/doc/api/test.md @@ -184,7 +184,7 @@ import { describe, it } from 'node:test'; const { describe, it } = require('node:test'); ``` -### `only` tests +## `only` tests If Node.js is started with the [`--test-only`][] command-line option, it is possible to skip all top level tests except for a selected subset by passing @@ -220,6 +220,42 @@ test('this test is not run', () => { }); ``` +## Filtering tests by name + +The [`--test-name-pattern`][] command-line option can be used to only run tests +whose name matches the provided pattern. Test name patterns are interpreted as +JavaScript regular expressions. The `--test-name-pattern` option can be +specified multiple times in order to run nested tests. For each test that is +executed, any corresponding test hooks, such as `beforeEach()`, are also +run. + +Given the following test file, starting Node.js with the +`--test-name-pattern="test [1-3]"` option would cause the test runner to execute +`test 1`, `test 2`, and `test 3`. If `test 1` did not match the test name +pattern, then its subtests would not execute, despite matching the pattern. The +same set of tests could also be executed by passing `--test-name-pattern` +multiple times (e.g. `--test-name-pattern="test 1"`, +`--test-name-pattern="test 2"`, etc.). + +```js +test('test 1', async (t) => { + await t.test('test 2'); + await t.test('test 3'); +}); + +test('Test 4', async (t) => { + await t.test('Test 5'); + await t.test('test 6'); +}); +``` + +Test name patterns can also be specified using regular expression literals. This +allows regular expression flags to be used. In the previous example, starting +Node.js with `--test-name-pattern="/test [4-5]/i"` would match `Test 4` and +`Test 5` because the pattern is case-insensitive. + +Test name patterns do not change the set of files that the test runner executes. + ## Extraneous asynchronous activity Once a test function finishes executing, the TAP results are output as quickly @@ -236,8 +272,8 @@ top level of the file's TAP output. The second `setImmediate()` creates an `uncaughtException` event. `uncaughtException` and `unhandledRejection` events originating from a completed -test are handled by the `test` module and reported as diagnostic warnings in -the top level of the file's TAP output. +test are marked as failed by the `test` module and reported as diagnostic +warnings in the top level of the file's TAP output. ```js test('a test that creates asynchronous activity', (t) => { @@ -255,6 +291,25 @@ test('a test that creates asynchronous activity', (t) => { }); ``` +## Watch mode + + + +> Stability: 1 - Experimental + +The Node.js test runner supports running in watch mode by passing the `--watch` flag: + +```bash +node --test --watch +``` + +In watch mode, the test runner will watch for changes to test files and +their dependencies. When a change is detected, the test runner will +rerun the tests affected by the change. +The test runner will continue to run until the process is terminated. + ## Running tests from the command line The Node.js test runner can be invoked from the command line by passing the @@ -316,6 +371,89 @@ Otherwise, the test is considered to be a failure. Test files must be executable by Node.js, but are not required to use the `node:test` module internally. +## Mocking + +The `node:test` module supports mocking during testing via a top-level `mock` +object. The following example creates a spy on a function that adds two numbers +together. The spy is then used to assert that the function was called as +expected. + +```mjs +import assert from 'node:assert'; +import { mock, test } from 'node:test'; + +test('spies on a function', () => { + const sum = mock.fn((a, b) => { + return a + b; + }); + + assert.strictEqual(sum.mock.calls.length, 0); + assert.strictEqual(sum(3, 4), 7); + assert.strictEqual(sum.mock.calls.length, 1); + + const call = sum.mock.calls[0]; + assert.deepStrictEqual(call.arguments, [3, 4]); + assert.strictEqual(call.result, 7); + assert.strictEqual(call.error, undefined); + + // Reset the globally tracked mocks. + mock.reset(); +}); +``` + +```cjs +'use strict'; +const assert = require('node:assert'); +const { mock, test } = require('node:test'); + +test('spies on a function', () => { + const sum = mock.fn((a, b) => { + return a + b; + }); + + assert.strictEqual(sum.mock.calls.length, 0); + assert.strictEqual(sum(3, 4), 7); + assert.strictEqual(sum.mock.calls.length, 1); + + const call = sum.mock.calls[0]; + assert.deepStrictEqual(call.arguments, [3, 4]); + assert.strictEqual(call.result, 7); + assert.strictEqual(call.error, undefined); + + // Reset the globally tracked mocks. + mock.reset(); +}); +``` + +The same mocking functionality is also exposed on the [`TestContext`][] object +of each test. The following example creates a spy on an object method using the +API exposed on the `TestContext`. The benefit of mocking via the test context is +that the test runner will automatically restore all mocked functionality once +the test finishes. + +```js +test('spies on an object method', (t) => { + const number = { + value: 5, + add(a) { + return this.value + a; + }, + }; + + t.mock.method(number, 'add'); + assert.strictEqual(number.add.mock.calls.length, 0); + assert.strictEqual(number.add(3), 8); + assert.strictEqual(number.add.mock.calls.length, 1); + + const call = number.add.mock.calls[0]; + + assert.deepStrictEqual(call.arguments, [3]); + assert.strictEqual(call.result, 8); + assert.strictEqual(call.target, undefined); + assert.strictEqual(call.this, number); +}); +``` + ## `run([options])` + +The `MockFunctionContext` class is used to inspect or manipulate the behavior of +mocks created via the [`MockTracker`][] APIs. + +### `ctx.calls` + + + +* {Array} + +A getter that returns a copy of the internal array used to track calls to the +mock. Each entry in the array is an object with the following properties. + +* `arguments` {Array} An array of the arguments passed to the mock function. +* `error` {any} If the mocked function threw then this property contains the + thrown value. **Default:** `undefined`. +* `result` {any} The value returned by the mocked function. +* `stack` {Error} An `Error` object whose stack can be used to determine the + callsite of the mocked function invocation. +* `target` {Function|undefined} If the mocked function is a constructor, this + field contains the class being constructed. Otherwise this will be + `undefined`. +* `this` {any} The mocked function's `this` value. + +### `ctx.callCount()` + + + +* Returns: {integer} The number of times that this mock has been invoked. + +This function returns the number of times that this mock has been invoked. This +function is more efficient than checking `ctx.calls.length` because `ctx.calls` +is a getter that creates a copy of the internal call tracking array. + +### `ctx.mockImplementation(implementation)` + + + +* `implementation` {Function|AsyncFunction} The function to be used as the + mock's new implementation. + +This function is used to change the behavior of an existing mock. + +The following example creates a mock function using `t.mock.fn()`, calls the +mock function, and then changes the mock implementation to a different function. + +```js +test('changes a mock behavior', (t) => { + let cnt = 0; + + function addOne() { + cnt++; + return cnt; + } + + function addTwo() { + cnt += 2; + return cnt; + } + + const fn = t.mock.fn(addOne); + + assert.strictEqual(fn(), 1); + fn.mock.mockImplementation(addTwo); + assert.strictEqual(fn(), 3); + assert.strictEqual(fn(), 5); +}); +``` + +### `ctx.mockImplementationOnce(implementation[, onCall])` + + + +* `implementation` {Function|AsyncFunction} The function to be used as the + mock's implementation for the invocation number specified by `onCall`. +* `onCall` {integer} The invocation number that will use `implementation`. If + the specified invocation has already occurred then an exception is thrown. + **Default:** The number of the next invocation. + +This function is used to change the behavior of an existing mock for a single +invocation. Once invocation `onCall` has occurred, the mock will revert to +whatever behavior it would have used had `mockImplementationOnce()` not been +called. + +The following example creates a mock function using `t.mock.fn()`, calls the +mock function, changes the mock implementation to a different function for the +next invocation, and then resumes its previous behavior. + +```js +test('changes a mock behavior once', (t) => { + let cnt = 0; + + function addOne() { + cnt++; + return cnt; + } + + function addTwo() { + cnt += 2; + return cnt; + } + + const fn = t.mock.fn(addOne); + + assert.strictEqual(fn(), 1); + fn.mock.mockImplementationOnce(addTwo); + assert.strictEqual(fn(), 3); + assert.strictEqual(fn(), 4); +}); +``` + +### `ctx.restore()` + + + +Resets the implementation of the mock function to its original behavior. The +mock can still be used after calling this function. + +## Class: `MockTracker` + + + +The `MockTracker` class is used to manage mocking functionality. The test runner +module provides a top level `mock` export which is a `MockTracker` instance. +Each test also provides its own `MockTracker` instance via the test context's +`mock` property. + +### `mock.fn([original[, implementation]][, options])` + + + +* `original` {Function|AsyncFunction} An optional function to create a mock on. + **Default:** A no-op function. +* `implementation` {Function|AsyncFunction} An optional function used as the + mock implementation for `original`. This is useful for creating mocks that + exhibit one behavior for a specified number of calls and then restore the + behavior of `original`. **Default:** The function specified by `original`. +* `options` {Object} Optional configuration options for the mock function. The + following properties are supported: + * `times` {integer} The number of times that the mock will use the behavior of + `implementation`. Once the mock function has been called `times` times, it + will automatically restore the behavior of `original`. This value must be an + integer greater than zero. **Default:** `Infinity`. +* Returns: {Proxy} The mocked function. The mocked function contains a special + `mock` property, which is an instance of [`MockFunctionContext`][], and can + be used for inspecting and changing the behavior of the mocked function. + +This function is used to create a mock function. + +The following example creates a mock function that increments a counter by one +on each invocation. The `times` option is used to modify the mock behavior such +that the first two invocations add two to the counter instead of one. + +```js +test('mocks a counting function', (t) => { + let cnt = 0; + + function addOne() { + cnt++; + return cnt; + } + + function addTwo() { + cnt += 2; + return cnt; + } + + const fn = t.mock.fn(addOne, addTwo, { times: 2 }); + + assert.strictEqual(fn(), 2); + assert.strictEqual(fn(), 4); + assert.strictEqual(fn(), 5); + assert.strictEqual(fn(), 6); +}); +``` + +### `mock.method(object, methodName[, implementation][, options])` + + + +* `object` {Object} The object whose method is being mocked. +* `methodName` {string|symbol} The identifier of the method on `object` to mock. + If `object[methodName]` is not a function, an error is thrown. +* `implementation` {Function|AsyncFunction} An optional function used as the + mock implementation for `object[methodName]`. **Default:** The original method + specified by `object[methodName]`. +* `options` {Object} Optional configuration options for the mock method. The + following properties are supported: + * `getter` {boolean} If `true`, `object[methodName]` is treated as a getter. + This option cannot be used with the `setter` option. **Default:** false. + * `setter` {boolean} If `true`, `object[methodName]` is treated as a setter. + This option cannot be used with the `getter` option. **Default:** false. + * `times` {integer} The number of times that the mock will use the behavior of + `implementation`. Once the mocked method has been called `times` times, it + will automatically restore the original behavior. This value must be an + integer greater than zero. **Default:** `Infinity`. +* Returns: {Proxy} The mocked method. The mocked method contains a special + `mock` property, which is an instance of [`MockFunctionContext`][], and can + be used for inspecting and changing the behavior of the mocked method. + +This function is used to create a mock on an existing object method. The +following example demonstrates how a mock is created on an existing object +method. + +```js +test('spies on an object method', (t) => { + const number = { + value: 5, + subtract(a) { + return this.value - a; + }, + }; + + t.mock.method(number, 'subtract'); + assert.strictEqual(number.subtract.mock.calls.length, 0); + assert.strictEqual(number.subtract(3), 2); + assert.strictEqual(number.subtract.mock.calls.length, 1); + + const call = number.subtract.mock.calls[0]; + + assert.deepStrictEqual(call.arguments, [3]); + assert.strictEqual(call.result, 2); + assert.strictEqual(call.error, undefined); + assert.strictEqual(call.target, undefined); + assert.strictEqual(call.this, number); +}); +``` + +### `mock.reset()` + + + +This function restores the default behavior of all mocks that were previously +created by this `MockTracker` and disassociates the mocks from the +`MockTracker` instance. Once disassociated, the mocks can still be used, but the +`MockTracker` instance can no longer be used to reset their behavior or +otherwise interact with them. + +After each test completes, this function is called on the test context's +`MockTracker`. If the global `MockTracker` is used extensively, calling this +function manually is recommended. + +### `mock.restoreAll()` + + + +This function restores the default behavior of all mocks that were previously +created by this `MockTracker`. Unlike `mock.reset()`, `mock.restoreAll()` does +not disassociate the mocks from the `MockTracker` instance. + ## Class: `TapStream`