mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 18:25:13 +08:00
test_runner: add mock call assertions to t.assert
Add t.assert.called(), t.assert.callCount(), t.assert.nthCalledWith() and t.assert.lastCalledWith() for asserting on mock function calls. Assisted-by: Claude Signed-off-by: Guilherme Araújo <arauujogui@gmail.com> PR-URL: https://github.com/nodejs/node/pull/66476 Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
1 parent
9a85290d86
commit
fcfb7ecc0b
4 files changed
+276
-1
No files matched your search
@@ -4359,6 +4359,46 @@ test('test', (t) => {
|
||||
});
|
||||
```
|
||||
|
||||
#### `context.assert.callCount(fn, times[, message])`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `fn` {Function} A mock function created by the test runner's mocking API.
|
||||
* `times` {integer} The expected number of calls.
|
||||
* `message` {string} Optional error message.
|
||||
|
||||
Asserts that the mock function `fn` has been called exactly `times` times.
|
||||
|
||||
```js
|
||||
test('mock was called twice', (t) => {
|
||||
const fn = t.mock.fn();
|
||||
fn();
|
||||
fn();
|
||||
t.assert.callCount(fn, 2);
|
||||
});
|
||||
```
|
||||
|
||||
#### `context.assert.called(fn[, message])`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `fn` {Function} A mock function created by the test runner's mocking API.
|
||||
* `message` {string} Optional error message.
|
||||
|
||||
Asserts that the mock function `fn` has been called at least once.
|
||||
|
||||
```js
|
||||
test('mock was called', (t) => {
|
||||
const fn = t.mock.fn();
|
||||
fn();
|
||||
t.assert.called(fn);
|
||||
});
|
||||
```
|
||||
|
||||
#### `context.assert.fileSnapshot(value, path[, options])`
|
||||
|
||||
<!-- YAML
|
||||
@@ -4398,6 +4438,52 @@ This function differs from `context.assert.snapshot()` in the following ways:
|
||||
These differences allow snapshot files to better support features such as syntax
|
||||
highlighting.
|
||||
|
||||
#### `context.assert.lastCalledWith(fn[, ...args])`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `fn` {Function} A mock function created by the test runner's mocking API.
|
||||
* `...args` {any} The expected arguments.
|
||||
|
||||
Asserts that the most recent call to the mock function `fn` received arguments
|
||||
deeply and strictly equal to `args`, using the same comparison as
|
||||
[`assert.deepStrictEqual()`][].
|
||||
|
||||
```js
|
||||
test('mock was last called with arguments', (t) => {
|
||||
const fn = t.mock.fn();
|
||||
fn(1);
|
||||
fn(2);
|
||||
t.assert.lastCalledWith(fn, 2);
|
||||
});
|
||||
```
|
||||
|
||||
#### `context.assert.nthCalledWith(fn, n[, ...args])`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `fn` {Function} A mock function created by the test runner's mocking API.
|
||||
* `n` {integer} The 1-based index of the call to check.
|
||||
* `...args` {any} The expected arguments.
|
||||
|
||||
Asserts that the `n`th call to the mock function `fn` received arguments
|
||||
deeply and strictly equal to `args`, using the same comparison as
|
||||
[`assert.deepStrictEqual()`][]. `n` starts at `1`, so
|
||||
`t.assert.nthCalledWith(fn, 1)` checks `fn.mock.calls[0]`.
|
||||
|
||||
```js
|
||||
test('mock was called with arguments on the second call', (t) => {
|
||||
const fn = t.mock.fn();
|
||||
fn(1);
|
||||
fn(2);
|
||||
t.assert.nthCalledWith(fn, 2, 2);
|
||||
});
|
||||
```
|
||||
|
||||
#### `context.assert.snapshot(value[, options])`
|
||||
|
||||
<!-- YAML
|
||||
@@ -5036,6 +5122,7 @@ test.describe('my suite', (suite) => {
|
||||
[`SuiteContext`]: #class-suitecontext
|
||||
[`TestContext`]: #class-testcontext
|
||||
[`TracingChannel`]: diagnostics_channel.md#class-tracingchannel
|
||||
[`assert.deepStrictEqual()`]: assert.md#assertdeepstrictequalactual-expected-message
|
||||
[`assert.throws`]: assert.md#assertthrowsfn-error-message
|
||||
[`context.diagnostic`]: #contextdiagnosticmessage
|
||||
[`context.log`]: #contextlogmessage-data
|
||||
|
||||
@@ -2,11 +2,19 @@
|
||||
const {
|
||||
SafeMap,
|
||||
} = primordials;
|
||||
const {
|
||||
codes: {
|
||||
ERR_INVALID_ARG_TYPE,
|
||||
},
|
||||
} = require('internal/errors');
|
||||
const {
|
||||
validateFunction,
|
||||
validateInteger,
|
||||
validateString,
|
||||
} = require('internal/validators');
|
||||
const { isDeepStrictEqual } = require('internal/util/comparisons');
|
||||
const assert = require('assert');
|
||||
const { AssertionError } = assert;
|
||||
const methodsToCopy = [
|
||||
'deepEqual',
|
||||
'deepStrictEqual',
|
||||
@@ -28,6 +36,98 @@ const methodsToCopy = [
|
||||
];
|
||||
let assertMap;
|
||||
|
||||
function getMockContext(fn) {
|
||||
const { MockFunctionContext } = require('internal/test_runner/mock/mock');
|
||||
const ctx = fn?.mock;
|
||||
|
||||
if (!(ctx instanceof MockFunctionContext)) {
|
||||
throw new ERR_INVALID_ARG_TYPE('fn', 'mock function', fn);
|
||||
}
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
function called(fn, message) {
|
||||
const count = getMockContext(fn).callCount();
|
||||
|
||||
if (count === 0) {
|
||||
throw new AssertionError({
|
||||
__proto__: null,
|
||||
message: message ?? 'Expected mock function to have been called',
|
||||
actual: count,
|
||||
operator: 'called',
|
||||
stackStartFn: called,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function callCount(fn, times, message) {
|
||||
validateInteger(times, 'times', 0);
|
||||
const count = getMockContext(fn).callCount();
|
||||
|
||||
if (count !== times) {
|
||||
throw new AssertionError({
|
||||
__proto__: null,
|
||||
message: message ??
|
||||
`Expected mock function to have been called ${times} times, but it was called ${count} times`,
|
||||
actual: count,
|
||||
expected: times,
|
||||
operator: 'callCount',
|
||||
stackStartFn: callCount,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function assertCalledWithAt(fn, n, args, stackStartFn) {
|
||||
const calls = getMockContext(fn).calls;
|
||||
|
||||
if (n > calls.length) {
|
||||
throw new AssertionError({
|
||||
__proto__: null,
|
||||
message: `Expected mock function to have been called at least ${n} times, ` +
|
||||
`but it was called ${calls.length} times`,
|
||||
actual: calls.length,
|
||||
expected: n,
|
||||
operator: stackStartFn.name,
|
||||
stackStartFn,
|
||||
});
|
||||
}
|
||||
|
||||
const actual = calls[n - 1].arguments;
|
||||
|
||||
if (!isDeepStrictEqual(actual, args)) {
|
||||
throw new AssertionError({
|
||||
__proto__: null,
|
||||
message: `Expected call ${n} of mock function to have been called with:`,
|
||||
actual,
|
||||
expected: args,
|
||||
operator: 'deepStrictEqual',
|
||||
stackStartFn,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function nthCalledWith(fn, n, ...args) {
|
||||
validateInteger(n, 'n', 1);
|
||||
assertCalledWithAt(fn, n, args, nthCalledWith);
|
||||
}
|
||||
|
||||
function lastCalledWith(fn, ...args) {
|
||||
const count = getMockContext(fn).callCount();
|
||||
|
||||
if (count === 0) {
|
||||
throw new AssertionError({
|
||||
__proto__: null,
|
||||
message: 'Expected mock function to have been called',
|
||||
actual: count,
|
||||
operator: 'lastCalledWith',
|
||||
stackStartFn: lastCalledWith,
|
||||
});
|
||||
}
|
||||
|
||||
assertCalledWithAt(fn, count, args, lastCalledWith);
|
||||
}
|
||||
|
||||
function getAssertionMap() {
|
||||
if (assertMap === undefined) {
|
||||
assertMap = new SafeMap();
|
||||
@@ -35,6 +135,11 @@ function getAssertionMap() {
|
||||
for (let i = 0; i < methodsToCopy.length; i++) {
|
||||
assertMap.set(methodsToCopy[i], assert[methodsToCopy[i]]);
|
||||
}
|
||||
|
||||
assertMap.set('called', called);
|
||||
assertMap.set('callCount', callCount);
|
||||
assertMap.set('lastCalledWith', lastCalledWith);
|
||||
assertMap.set('nthCalledWith', nthCalledWith);
|
||||
}
|
||||
|
||||
return assertMap;
|
||||
|
||||
@@ -1118,5 +1118,6 @@ if (!enableModuleMocking) {
|
||||
|
||||
module.exports = {
|
||||
ensureNodeScheme,
|
||||
MockFunctionContext,
|
||||
MockTracker,
|
||||
};
|
||||
@@ -11,10 +11,92 @@ test('expected methods are on t.assert', (t) => {
|
||||
'options',
|
||||
];
|
||||
const assertKeys = Object.keys(assert).filter((key) => !uncopiedKeys.includes(key));
|
||||
const expectedKeys = ['snapshot', 'fileSnapshot'].concat(assertKeys).sort();
|
||||
const expectedKeys = [
|
||||
'snapshot',
|
||||
'fileSnapshot',
|
||||
'called',
|
||||
'callCount',
|
||||
'lastCalledWith',
|
||||
'nthCalledWith',
|
||||
].concat(assertKeys).sort();
|
||||
assert.deepStrictEqual(Object.keys(t.assert).sort(), expectedKeys);
|
||||
});
|
||||
|
||||
test('t.assert.ok correctly parses the stacktrace', (t) => {
|
||||
t.assert.throws(() => t.assert.ok(1 === 2), /t\.assert\.ok\(1 === 2\)/);
|
||||
});
|
||||
|
||||
test('t.assert.called', (t) => {
|
||||
const fn = t.mock.fn();
|
||||
t.assert.throws(() => t.assert.called(fn), {
|
||||
code: 'ERR_ASSERTION',
|
||||
message: 'Expected mock function to have been called',
|
||||
});
|
||||
fn();
|
||||
t.assert.called(fn);
|
||||
t.assert.throws(() => t.assert.called(() => {}), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
});
|
||||
});
|
||||
|
||||
test('t.assert.callCount', (t) => {
|
||||
const fn = t.mock.fn();
|
||||
t.assert.callCount(fn, 0);
|
||||
fn();
|
||||
fn();
|
||||
t.assert.callCount(fn, 2);
|
||||
t.assert.throws(() => t.assert.callCount(fn, 1), {
|
||||
code: 'ERR_ASSERTION',
|
||||
message: 'Expected mock function to have been called 1 times, but it was called 2 times',
|
||||
});
|
||||
t.assert.throws(() => t.assert.callCount(fn, -1), {
|
||||
code: 'ERR_OUT_OF_RANGE',
|
||||
});
|
||||
});
|
||||
|
||||
test('t.assert.nthCalledWith', (t) => {
|
||||
const fn = t.mock.fn();
|
||||
fn(1);
|
||||
fn({ a: 2 });
|
||||
t.assert.nthCalledWith(fn, 1, 1);
|
||||
t.assert.nthCalledWith(fn, 2, { a: 2 });
|
||||
t.assert.throws(() => t.assert.nthCalledWith(fn, 2, { a: 3 }), {
|
||||
code: 'ERR_ASSERTION',
|
||||
message: /^Expected call 2 of mock function to have been called with:/,
|
||||
actual: [{ a: 2 }],
|
||||
expected: [{ a: 3 }],
|
||||
});
|
||||
t.assert.throws(() => t.assert.nthCalledWith(fn, 3, 1), {
|
||||
code: 'ERR_ASSERTION',
|
||||
message: 'Expected mock function to have been called at least 3 times, but it was called 2 times',
|
||||
});
|
||||
t.assert.throws(() => t.assert.nthCalledWith(fn, 0, 1), {
|
||||
code: 'ERR_OUT_OF_RANGE',
|
||||
});
|
||||
});
|
||||
|
||||
test('t.assert.lastCalledWith', (t) => {
|
||||
const fn = t.mock.fn();
|
||||
t.assert.throws(() => t.assert.lastCalledWith(fn), {
|
||||
code: 'ERR_ASSERTION',
|
||||
message: 'Expected mock function to have been called',
|
||||
});
|
||||
fn(1);
|
||||
fn(2);
|
||||
t.assert.lastCalledWith(fn, 2);
|
||||
t.assert.throws(() => t.assert.lastCalledWith(fn, 1), {
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: [2],
|
||||
expected: [1],
|
||||
});
|
||||
});
|
||||
|
||||
test('mock matchers count towards t.plan()', (t) => {
|
||||
t.plan(4);
|
||||
const fn = t.mock.fn();
|
||||
fn(1);
|
||||
t.assert.called(fn);
|
||||
t.assert.callCount(fn, 1);
|
||||
t.assert.nthCalledWith(fn, 1, 1);
|
||||
t.assert.lastCalledWith(fn, 1);
|
||||
});
|
||||
Reference in new issue
Block a user