Files
Guilherme Araújo fcfb7ecc0b 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>
2026-10-06 05:55:47 +00:00

156 lines
3.5 KiB
JavaScript

'use strict';
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',
'doesNotMatch',
'doesNotReject',
'doesNotThrow',
'equal',
'fail',
'ifError',
'match',
'notDeepEqual',
'notDeepStrictEqual',
'notEqual',
'notStrictEqual',
'partialDeepStrictEqual',
'rejects',
'strictEqual',
'throws',
];
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();
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;
}
function register(name, fn) {
validateString(name, 'name');
validateFunction(fn, 'fn');
const map = getAssertionMap();
map.set(name, fn);
}
module.exports = { getAssertionMap, register };