node/test/parallel/test-runner-mocking.js
Michaël Zasso 7e6d92c485
tools: update ESLint to v9 and use flat config
Closes: https://github.com/nodejs/node/issues/52567
PR-URL: https://github.com/nodejs/node/pull/52780
Fixes: https://github.com/nodejs/node/issues/52567
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Yagiz Nizipli <yagiz.nizipli@sentry.io>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
2024-05-23 19:45:18 +00:00

1057 lines
28 KiB
JavaScript

'use strict';
const common = require('../common');
const assert = require('node:assert');
const { mock, test } = require('node:test');
test('spies on a function', (t) => {
const sum = t.mock.fn((arg1, arg2) => {
return arg1 + arg2;
});
assert.strictEqual(sum.mock.calls.length, 0);
assert.strictEqual(sum(3, 4), 7);
assert.strictEqual(sum.call(1000, 9, 1), 10);
assert.strictEqual(sum.mock.calls.length, 2);
let call = sum.mock.calls[0];
assert.deepStrictEqual(call.arguments, [3, 4]);
assert.strictEqual(call.error, undefined);
assert.strictEqual(call.result, 7);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, undefined);
call = sum.mock.calls[1];
assert.deepStrictEqual(call.arguments, [9, 1]);
assert.strictEqual(call.error, undefined);
assert.strictEqual(call.result, 10);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, 1000);
});
test('spies on a bound function', (t) => {
const bound = function(arg1, arg2) {
return this + arg1 + arg2;
}.bind(50);
const sum = t.mock.fn(bound);
assert.strictEqual(sum.mock.calls.length, 0);
assert.strictEqual(sum(3, 4), 57);
assert.strictEqual(sum(9, 1), 60);
assert.strictEqual(sum.mock.calls.length, 2);
let call = sum.mock.calls[0];
assert.deepStrictEqual(call.arguments, [3, 4]);
assert.strictEqual(call.result, 57);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, undefined);
call = sum.mock.calls[1];
assert.deepStrictEqual(call.arguments, [9, 1]);
assert.strictEqual(call.result, 60);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, undefined);
});
test('spies on a constructor', (t) => {
class ParentClazz {
constructor(c) {
this.c = c;
}
}
class Clazz extends ParentClazz {
#privateValue;
constructor(a, b) {
super(a + b);
this.a = a;
this.#privateValue = b;
}
getPrivateValue() {
return this.#privateValue;
}
}
const ctor = t.mock.fn(Clazz);
const instance = new ctor(42, 85);
assert(instance instanceof Clazz);
assert(instance instanceof ParentClazz);
assert.strictEqual(instance.a, 42);
assert.strictEqual(instance.getPrivateValue(), 85);
assert.strictEqual(instance.c, 127);
assert.strictEqual(ctor.mock.calls.length, 1);
const call = ctor.mock.calls[0];
assert.deepStrictEqual(call.arguments, [42, 85]);
assert.strictEqual(call.error, undefined);
assert.strictEqual(call.result, instance);
assert.strictEqual(call.target, Clazz);
assert.strictEqual(call.this, instance);
});
test('a no-op spy function is created by default', (t) => {
const fn = t.mock.fn();
assert.strictEqual(fn.mock.calls.length, 0);
assert.strictEqual(fn(3, 4), undefined);
assert.strictEqual(fn.mock.calls.length, 1);
const call = fn.mock.calls[0];
assert.deepStrictEqual(call.arguments, [3, 4]);
assert.strictEqual(call.result, undefined);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, undefined);
});
test('internal no-op function can be reused', (t) => {
const fn1 = t.mock.fn();
fn1.prop = true;
const fn2 = t.mock.fn();
fn1(1);
fn2(2);
fn1(3);
assert.notStrictEqual(fn1.mock, fn2.mock);
assert.strictEqual(fn1.mock.calls.length, 2);
assert.strictEqual(fn2.mock.calls.length, 1);
assert.strictEqual(fn1.prop, true);
assert.strictEqual(fn2.prop, undefined);
});
test('functions can be mocked multiple times at once', (t) => {
function sum(a, b) {
return a + b;
}
function difference(a, b) {
return a - b;
}
function product(a, b) {
return a * b;
}
const fn1 = t.mock.fn(sum, difference);
const fn2 = t.mock.fn(sum, product);
assert.strictEqual(fn1(5, 3), 2);
assert.strictEqual(fn2(5, 3), 15);
assert.strictEqual(fn2(4, 2), 8);
assert(!('mock' in sum));
assert(!('mock' in difference));
assert(!('mock' in product));
assert.notStrictEqual(fn1.mock, fn2.mock);
assert.strictEqual(fn1.mock.calls.length, 1);
assert.strictEqual(fn2.mock.calls.length, 2);
});
test('internal no-op function can be reused as methods', (t) => {
const obj = {
_foo: 5,
_bar: 9,
foo() {
return this._foo;
},
bar() {
return this._bar;
},
};
t.mock.method(obj, 'foo');
obj.foo.prop = true;
t.mock.method(obj, 'bar');
assert.strictEqual(obj.foo(), 5);
assert.strictEqual(obj.bar(), 9);
assert.strictEqual(obj.bar(), 9);
assert.notStrictEqual(obj.foo.mock, obj.bar.mock);
assert.strictEqual(obj.foo.mock.calls.length, 1);
assert.strictEqual(obj.bar.mock.calls.length, 2);
assert.strictEqual(obj.foo.prop, true);
assert.strictEqual(obj.bar.prop, undefined);
});
test('methods can be mocked multiple times but not at the same time', (t) => {
const obj = {
offset: 3,
sum(a, b) {
return this.offset + a + b;
},
};
function difference(a, b) {
return this.offset + (a - b);
}
function product(a, b) {
return this.offset + a * b;
}
const originalSum = obj.sum;
const fn1 = t.mock.method(obj, 'sum', difference);
assert.strictEqual(obj.sum(5, 3), 5);
assert.strictEqual(obj.sum(5, 1), 7);
assert.strictEqual(obj.sum, fn1);
assert.notStrictEqual(fn1.mock, undefined);
assert.strictEqual(originalSum.mock, undefined);
assert.strictEqual(difference.mock, undefined);
assert.strictEqual(product.mock, undefined);
assert.strictEqual(fn1.mock.calls.length, 2);
const fn2 = t.mock.method(obj, 'sum', product);
assert.strictEqual(obj.sum(5, 3), 18);
assert.strictEqual(obj.sum, fn2);
assert.notStrictEqual(fn1, fn2);
assert.strictEqual(fn2.mock.calls.length, 1);
obj.sum.mock.restore();
assert.strictEqual(obj.sum, fn1);
obj.sum.mock.restore();
assert.strictEqual(obj.sum, originalSum);
assert.strictEqual(obj.sum.mock, undefined);
});
test('spies on an object method', (t) => {
const obj = {
prop: 5,
method(a, b) {
return a + b + this.prop;
},
};
assert.strictEqual(obj.method(1, 3), 9);
t.mock.method(obj, 'method');
assert.strictEqual(obj.method.mock.calls.length, 0);
assert.strictEqual(obj.method(1, 3), 9);
const call = obj.method.mock.calls[0];
assert.deepStrictEqual(call.arguments, [1, 3]);
assert.strictEqual(call.result, 9);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, obj);
assert.strictEqual(obj.method.mock.restore(), undefined);
assert.strictEqual(obj.method(1, 3), 9);
assert.strictEqual(obj.method.mock, undefined);
});
test('spies on a getter', (t) => {
const obj = {
prop: 5,
get method() {
return this.prop;
},
};
assert.strictEqual(obj.method, 5);
const getter = t.mock.method(obj, 'method', { getter: true });
assert.strictEqual(getter.mock.calls.length, 0);
assert.strictEqual(obj.method, 5);
const call = getter.mock.calls[0];
assert.deepStrictEqual(call.arguments, []);
assert.strictEqual(call.result, 5);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, obj);
assert.strictEqual(getter.mock.restore(), undefined);
assert.strictEqual(obj.method, 5);
});
test('spies on a setter', (t) => {
const obj = {
prop: 100,
// eslint-disable-next-line accessor-pairs
set method(val) {
this.prop = val;
},
};
assert.strictEqual(obj.prop, 100);
obj.method = 88;
assert.strictEqual(obj.prop, 88);
const setter = t.mock.method(obj, 'method', { setter: true });
assert.strictEqual(setter.mock.calls.length, 0);
obj.method = 77;
assert.strictEqual(obj.prop, 77);
assert.strictEqual(setter.mock.calls.length, 1);
const call = setter.mock.calls[0];
assert.deepStrictEqual(call.arguments, [77]);
assert.strictEqual(call.result, undefined);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, obj);
assert.strictEqual(setter.mock.restore(), undefined);
assert.strictEqual(obj.prop, 77);
obj.method = 65;
assert.strictEqual(obj.prop, 65);
});
test('spy functions can be bound', (t) => {
const sum = t.mock.fn(function(arg1, arg2) {
return this + arg1 + arg2;
});
const bound = sum.bind(1000);
assert.strictEqual(bound(9, 1), 1010);
assert.strictEqual(sum.mock.calls.length, 1);
const call = sum.mock.calls[0];
assert.deepStrictEqual(call.arguments, [9, 1]);
assert.strictEqual(call.result, 1010);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, 1000);
assert.strictEqual(sum.mock.restore(), undefined);
assert.strictEqual(sum.bind(0)(2, 11), 13);
});
test('mocks prototype methods on an instance', async (t) => {
class Runner {
async someTask(msg) {
return Promise.resolve(msg);
}
async method(msg) {
await this.someTask(msg);
return msg;
}
}
const msg = 'ok';
const obj = new Runner();
assert.strictEqual(await obj.method(msg), msg);
t.mock.method(obj, obj.someTask.name);
assert.strictEqual(obj.someTask.mock.calls.length, 0);
assert.strictEqual(await obj.method(msg), msg);
const call = obj.someTask.mock.calls[0];
assert.deepStrictEqual(call.arguments, [msg]);
assert.strictEqual(await call.result, msg);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, obj);
const obj2 = new Runner();
// Ensure that a brand new instance is not mocked
assert.strictEqual(
obj2.someTask.mock,
undefined
);
assert.strictEqual(obj.someTask.mock.restore(), undefined);
assert.strictEqual(await obj.method(msg), msg);
assert.strictEqual(obj.someTask.mock, undefined);
assert.strictEqual(Runner.prototype.someTask.mock, undefined);
});
test('spies on async static class methods', async (t) => {
class Runner {
static async someTask(msg) {
return Promise.resolve(msg);
}
static async method(msg) {
await this.someTask(msg);
return msg;
}
}
const msg = 'ok';
assert.strictEqual(await Runner.method(msg), msg);
t.mock.method(Runner, Runner.someTask.name);
assert.strictEqual(Runner.someTask.mock.calls.length, 0);
assert.strictEqual(await Runner.method(msg), msg);
const call = Runner.someTask.mock.calls[0];
assert.deepStrictEqual(call.arguments, [msg]);
assert.strictEqual(await call.result, msg);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, Runner);
assert.strictEqual(Runner.someTask.mock.restore(), undefined);
assert.strictEqual(await Runner.method(msg), msg);
assert.strictEqual(Runner.someTask.mock, undefined);
assert.strictEqual(Runner.prototype.someTask, undefined);
});
test('given null to a mock.method it throws a invalid argument error', (t) => {
assert.throws(() => t.mock.method(null, {}), { code: 'ERR_INVALID_ARG_TYPE' });
});
test('it should throw given an inexistent property on a object instance', (t) => {
assert.throws(() => t.mock.method({ abc: 0 }, 'non-existent'), {
code: 'ERR_INVALID_ARG_VALUE'
});
});
test('spy functions can be used on classes inheritance', (t) => {
// Makes sure that having a null-prototype doesn't throw our system off
class A extends null {
static someTask(msg) {
return msg;
}
static method(msg) {
return this.someTask(msg);
}
}
class B extends A {}
class C extends B {}
const msg = 'ok';
assert.strictEqual(C.method(msg), msg);
t.mock.method(C, C.someTask.name);
assert.strictEqual(C.someTask.mock.calls.length, 0);
assert.strictEqual(C.method(msg), msg);
const call = C.someTask.mock.calls[0];
assert.deepStrictEqual(call.arguments, [msg]);
assert.strictEqual(call.result, msg);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, C);
assert.strictEqual(C.someTask.mock.restore(), undefined);
assert.strictEqual(C.method(msg), msg);
assert.strictEqual(C.someTask.mock, undefined);
});
test('spy functions don\'t affect the prototype chain', (t) => {
class A {
static someTask(msg) {
return msg;
}
}
class B extends A {}
class C extends B {}
const msg = 'ok';
const ABeforeMockIsUnchanged = Object.getOwnPropertyDescriptor(A, A.someTask.name);
const BBeforeMockIsUnchanged = Object.getOwnPropertyDescriptor(B, B.someTask.name);
const CBeforeMockShouldNotHaveDesc = Object.getOwnPropertyDescriptor(C, C.someTask.name);
t.mock.method(C, C.someTask.name);
C.someTask(msg);
const BAfterMockIsUnchanged = Object.getOwnPropertyDescriptor(B, B.someTask.name);
const AAfterMockIsUnchanged = Object.getOwnPropertyDescriptor(A, A.someTask.name);
const CAfterMockHasDescriptor = Object.getOwnPropertyDescriptor(C, C.someTask.name);
assert.strictEqual(CBeforeMockShouldNotHaveDesc, undefined);
assert.ok(CAfterMockHasDescriptor);
assert.deepStrictEqual(ABeforeMockIsUnchanged, AAfterMockIsUnchanged);
assert.strictEqual(BBeforeMockIsUnchanged, BAfterMockIsUnchanged);
assert.strictEqual(BBeforeMockIsUnchanged, undefined);
assert.strictEqual(C.someTask.mock.restore(), undefined);
const CAfterRestoreKeepsDescriptor = Object.getOwnPropertyDescriptor(C, C.someTask.name);
assert.ok(CAfterRestoreKeepsDescriptor);
});
test('mocked functions report thrown errors', (t) => {
const testError = new Error('test error');
const fn = t.mock.fn(() => {
throw testError;
});
assert.throws(fn, /test error/);
assert.strictEqual(fn.mock.calls.length, 1);
const call = fn.mock.calls[0];
assert.deepStrictEqual(call.arguments, []);
assert.strictEqual(call.error, testError);
assert.strictEqual(call.result, undefined);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, undefined);
});
test('mocked constructors report thrown errors', (t) => {
const testError = new Error('test error');
class Clazz {
constructor() {
throw testError;
}
}
const ctor = t.mock.fn(Clazz);
assert.throws(() => {
new ctor();
}, /test error/);
assert.strictEqual(ctor.mock.calls.length, 1);
const call = ctor.mock.calls[0];
assert.deepStrictEqual(call.arguments, []);
assert.strictEqual(call.error, testError);
assert.strictEqual(call.result, undefined);
assert.strictEqual(call.target, Clazz);
assert.strictEqual(call.this, undefined);
});
test('mocks a function', (t) => {
const sum = (arg1, arg2) => arg1 + arg2;
const difference = (arg1, arg2) => arg1 - arg2;
const fn = t.mock.fn(sum, difference);
assert.strictEqual(fn.mock.calls.length, 0);
assert.strictEqual(fn(3, 4), -1);
assert.strictEqual(fn(9, 1), 8);
assert.strictEqual(fn.mock.calls.length, 2);
let call = fn.mock.calls[0];
assert.deepStrictEqual(call.arguments, [3, 4]);
assert.strictEqual(call.result, -1);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, undefined);
call = fn.mock.calls[1];
assert.deepStrictEqual(call.arguments, [9, 1]);
assert.strictEqual(call.result, 8);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, undefined);
assert.strictEqual(fn.mock.restore(), undefined);
assert.strictEqual(fn(2, 11), 13);
});
test('mocks a constructor', (t) => {
class ParentClazz {
constructor(c) {
this.c = c;
}
}
class Clazz extends ParentClazz {
#privateValue;
constructor(a, b) {
super(a + b);
this.a = a;
this.#privateValue = b;
}
getPrivateValue() {
return this.#privateValue;
}
}
class MockClazz {
// eslint-disable-next-line no-unused-private-class-members
#privateValue;
constructor(z) {
this.z = z;
}
}
const ctor = t.mock.fn(Clazz, MockClazz);
const instance = new ctor(42, 85);
assert(!(instance instanceof MockClazz));
assert(instance instanceof Clazz);
assert(instance instanceof ParentClazz);
assert.strictEqual(instance.a, undefined);
assert.strictEqual(instance.c, undefined);
assert.strictEqual(instance.z, 42);
assert.strictEqual(ctor.mock.calls.length, 1);
const call = ctor.mock.calls[0];
assert.deepStrictEqual(call.arguments, [42, 85]);
assert.strictEqual(call.result, instance);
assert.strictEqual(call.target, Clazz);
assert.strictEqual(call.this, instance);
assert.throws(() => {
instance.getPrivateValue();
}, /TypeError: Cannot read private member #privateValue /);
});
test('mocks an object method', (t) => {
const obj = {
prop: 5,
method(a, b) {
return a + b + this.prop;
},
};
function mockMethod(a) {
return a + this.prop;
}
assert.strictEqual(obj.method(1, 3), 9);
t.mock.method(obj, 'method', mockMethod);
assert.strictEqual(obj.method.mock.calls.length, 0);
assert.strictEqual(obj.method(1, 3), 6);
const call = obj.method.mock.calls[0];
assert.deepStrictEqual(call.arguments, [1, 3]);
assert.strictEqual(call.result, 6);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, obj);
assert.strictEqual(obj.method.mock.restore(), undefined);
assert.strictEqual(obj.method(1, 3), 9);
assert.strictEqual(obj.method.mock, undefined);
});
test('mocks a getter', (t) => {
const obj = {
prop: 5,
get method() {
return this.prop;
},
};
function mockMethod() {
return this.prop - 1;
}
assert.strictEqual(obj.method, 5);
const getter = t.mock.method(obj, 'method', mockMethod, { getter: true });
assert.strictEqual(getter.mock.calls.length, 0);
assert.strictEqual(obj.method, 4);
const call = getter.mock.calls[0];
assert.deepStrictEqual(call.arguments, []);
assert.strictEqual(call.result, 4);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, obj);
assert.strictEqual(getter.mock.restore(), undefined);
assert.strictEqual(obj.method, 5);
});
test('mocks a setter', (t) => {
const obj = {
prop: 100,
// eslint-disable-next-line accessor-pairs
set method(val) {
this.prop = val;
},
};
function mockMethod(val) {
this.prop = -val;
}
assert.strictEqual(obj.prop, 100);
obj.method = 88;
assert.strictEqual(obj.prop, 88);
const setter = t.mock.method(obj, 'method', mockMethod, { setter: true });
assert.strictEqual(setter.mock.calls.length, 0);
obj.method = 77;
assert.strictEqual(obj.prop, -77);
assert.strictEqual(setter.mock.calls.length, 1);
const call = setter.mock.calls[0];
assert.deepStrictEqual(call.arguments, [77]);
assert.strictEqual(call.result, undefined);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, obj);
assert.strictEqual(setter.mock.restore(), undefined);
assert.strictEqual(obj.prop, -77);
obj.method = 65;
assert.strictEqual(obj.prop, 65);
});
test('mocks a getter with syntax sugar', (t) => {
const obj = {
prop: 5,
get method() {
return this.prop;
},
};
function mockMethod() {
return this.prop - 1;
}
const getter = t.mock.getter(obj, 'method', mockMethod);
assert.strictEqual(getter.mock.calls.length, 0);
assert.strictEqual(obj.method, 4);
const call = getter.mock.calls[0];
assert.deepStrictEqual(call.arguments, []);
assert.strictEqual(call.result, 4);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, obj);
assert.strictEqual(getter.mock.restore(), undefined);
assert.strictEqual(obj.method, 5);
});
test('mocks a setter with syntax sugar', (t) => {
const obj = {
prop: 100,
// eslint-disable-next-line accessor-pairs
set method(val) {
this.prop = val;
},
};
function mockMethod(val) {
this.prop = -val;
}
assert.strictEqual(obj.prop, 100);
obj.method = 88;
assert.strictEqual(obj.prop, 88);
const setter = t.mock.setter(obj, 'method', mockMethod);
assert.strictEqual(setter.mock.calls.length, 0);
obj.method = 77;
assert.strictEqual(obj.prop, -77);
assert.strictEqual(setter.mock.calls.length, 1);
const call = setter.mock.calls[0];
assert.deepStrictEqual(call.arguments, [77]);
assert.strictEqual(call.result, undefined);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, obj);
assert.strictEqual(setter.mock.restore(), undefined);
assert.strictEqual(obj.prop, -77);
obj.method = 65;
assert.strictEqual(obj.prop, 65);
});
test('mocked functions match name and length', (t) => {
function getNameAndLength(fn) {
return {
name: Object.getOwnPropertyDescriptor(fn, 'name'),
length: Object.getOwnPropertyDescriptor(fn, 'length'),
};
}
function func1() {}
const func2 = function(a) {}; // eslint-disable-line func-style
const arrow = (a, b, c) => {};
const obj = { method(a, b) {} };
assert.deepStrictEqual(
getNameAndLength(func1),
getNameAndLength(t.mock.fn(func1))
);
assert.deepStrictEqual(
getNameAndLength(func2),
getNameAndLength(t.mock.fn(func2))
);
assert.deepStrictEqual(
getNameAndLength(arrow),
getNameAndLength(t.mock.fn(arrow))
);
assert.deepStrictEqual(
getNameAndLength(obj.method),
getNameAndLength(t.mock.method(obj, 'method', func1))
);
});
test('method() fails if method cannot be redefined', (t) => {
const obj = {
prop: 5,
};
Object.defineProperty(obj, 'method', {
configurable: false,
value(a, b) {
return a + b + this.prop;
}
});
function mockMethod(a) {
return a + this.prop;
}
assert.throws(() => {
t.mock.method(obj, 'method', mockMethod);
}, /Cannot redefine property: method/);
assert.strictEqual(obj.method(1, 3), 9);
assert.strictEqual(obj.method.mock, undefined);
});
test('method() fails if field is a property instead of a method', (t) => {
const obj = {
prop: 5,
method: 100,
};
function mockMethod(a) {
return a + this.prop;
}
assert.throws(() => {
t.mock.method(obj, 'method', mockMethod);
}, /The argument 'methodName' must be a method/);
assert.strictEqual(obj.method, 100);
assert.strictEqual(obj.method.mock, undefined);
});
test('mocks can be auto-restored', (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);
});
test('mock implementation can be changed dynamically', (t) => {
let cnt = 0;
function addOne() {
cnt++;
return cnt;
}
function addTwo() {
cnt += 2;
return cnt;
}
function addThree() {
cnt += 3;
return cnt;
}
const fn = t.mock.fn(addOne);
assert.strictEqual(fn.mock.callCount(), 0);
assert.strictEqual(fn(), 1);
assert.strictEqual(fn(), 2);
assert.strictEqual(fn(), 3);
assert.strictEqual(fn.mock.callCount(), 3);
fn.mock.mockImplementation(addTwo);
assert.strictEqual(fn(), 5);
assert.strictEqual(fn(), 7);
assert.strictEqual(fn.mock.callCount(), 5);
fn.mock.restore();
assert.strictEqual(fn(), 8);
assert.strictEqual(fn(), 9);
assert.strictEqual(fn.mock.callCount(), 7);
assert.throws(() => {
fn.mock.mockImplementationOnce(common.mustNotCall(), 6);
}, /The value of "onCall" is out of range\. It must be >= 7/);
fn.mock.mockImplementationOnce(addThree, 7);
fn.mock.mockImplementationOnce(addTwo, 8);
assert.strictEqual(fn(), 12);
assert.strictEqual(fn(), 14);
assert.strictEqual(fn(), 15);
assert.strictEqual(fn.mock.callCount(), 10);
fn.mock.mockImplementationOnce(addThree);
assert.strictEqual(fn(), 18);
assert.strictEqual(fn(), 19);
assert.strictEqual(fn.mock.callCount(), 12);
});
test('local mocks are auto restored after the test finishes', async (t) => {
const obj = {
foo() {},
bar() {},
};
const originalFoo = obj.foo;
const originalBar = obj.bar;
assert.strictEqual(originalFoo, obj.foo);
assert.strictEqual(originalBar, obj.bar);
const mockFoo = t.mock.method(obj, 'foo');
assert.strictEqual(mockFoo, obj.foo);
assert.notStrictEqual(originalFoo, obj.foo);
assert.strictEqual(originalBar, obj.bar);
t.beforeEach(() => {
assert.strictEqual(mockFoo, obj.foo);
assert.strictEqual(originalBar, obj.bar);
});
t.afterEach(() => {
assert.strictEqual(mockFoo, obj.foo);
assert.notStrictEqual(originalBar, obj.bar);
});
await t.test('creates mocks that are auto restored', (t) => {
const mockBar = t.mock.method(obj, 'bar');
assert.strictEqual(mockFoo, obj.foo);
assert.strictEqual(mockBar, obj.bar);
assert.notStrictEqual(originalBar, obj.bar);
});
assert.strictEqual(mockFoo, obj.foo);
assert.strictEqual(originalBar, obj.bar);
});
test('reset mock calls', (t) => {
const sum = (arg1, arg2) => arg1 + arg2;
const difference = (arg1, arg2) => arg1 - arg2;
const fn = t.mock.fn(sum, difference);
assert.strictEqual(fn(1, 2), -1);
assert.strictEqual(fn(2, 1), 1);
assert.strictEqual(fn.mock.calls.length, 2);
assert.strictEqual(fn.mock.callCount(), 2);
fn.mock.resetCalls();
assert.strictEqual(fn.mock.calls.length, 0);
assert.strictEqual(fn.mock.callCount(), 0);
assert.strictEqual(fn(3, 2), 1);
});
test('uses top level mock', () => {
function sum(a, b) {
return a + b;
}
function difference(a, b) {
return a - b;
}
const fn = mock.fn(sum, difference);
assert.strictEqual(fn.mock.calls.length, 0);
assert.strictEqual(fn(3, 4), -1);
assert.strictEqual(fn.mock.calls.length, 1);
mock.reset();
assert.strictEqual(fn(3, 4), 7);
assert.strictEqual(fn.mock.calls.length, 2);
});
test('the getter and setter options cannot be used together', (t) => {
assert.throws(() => {
t.mock.method({}, 'method', { getter: true, setter: true });
}, /The property 'options\.setter' cannot be used with 'options\.getter'/);
});
test('method names must be strings or symbols', (t) => {
const symbol = Symbol();
const obj = {
method() {},
[symbol]() {},
};
t.mock.method(obj, 'method');
t.mock.method(obj, symbol);
assert.throws(() => {
t.mock.method(obj, {});
}, /The "methodName" argument must be one of type string or symbol/);
});
test('the times option must be an integer >= 1', (t) => {
assert.throws(() => {
t.mock.fn({ times: null });
}, /The "options\.times" property must be of type number/);
assert.throws(() => {
t.mock.fn({ times: 0 });
}, /The value of "options\.times" is out of range/);
assert.throws(() => {
t.mock.fn(() => {}, { times: 3.14159 });
}, /The value of "options\.times" is out of range/);
});
test('spies on a class prototype method', (t) => {
class Clazz {
constructor(c) {
this.c = c;
}
getC() {
return this.c;
}
}
const instance = new Clazz(85);
assert.strictEqual(instance.getC(), 85);
t.mock.method(Clazz.prototype, 'getC');
assert.strictEqual(instance.getC.mock.calls.length, 0);
assert.strictEqual(instance.getC(), 85);
assert.strictEqual(instance.getC.mock.calls.length, 1);
assert.strictEqual(Clazz.prototype.getC.mock.calls.length, 1);
const call = instance.getC.mock.calls[0];
assert.deepStrictEqual(call.arguments, []);
assert.strictEqual(call.result, 85);
assert.strictEqual(call.error, undefined);
assert.strictEqual(call.target, undefined);
assert.strictEqual(call.this, instance);
});
test('getter() fails if getter options set to false', (t) => {
assert.throws(() => {
t.mock.getter({}, 'method', { getter: false });
}, /The property 'options\.getter' cannot be false/);
});
test('setter() fails if setter options set to false', (t) => {
assert.throws(() => {
t.mock.setter({}, 'method', { setter: false });
}, /The property 'options\.setter' cannot be false/);
});
test('getter() fails if setter options is true', (t) => {
assert.throws(() => {
t.mock.getter({}, 'method', { setter: true });
}, /The property 'options\.setter' cannot be used with 'options\.getter'/);
});
test('setter() fails if getter options is true', (t) => {
assert.throws(() => {
t.mock.setter({}, 'method', { getter: true });
}, /The property 'options\.setter' cannot be used with 'options\.getter'/);
});