| '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 an 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'/); |
| }); |
| |
| test('spies on a property', (t) => { |
| const obj = { foo: 42 }; |
| const prop = t.mock.property(obj, 'foo', 100); |
| |
| assert.strictEqual(obj.foo, 100); |
| assert.strictEqual(prop.mock.accessCount(), 1); |
| assert.strictEqual(prop.mock.accesses[0].type, 'get'); |
| assert.strictEqual(prop.mock.accesses[0].value, 100); |
| |
| obj.foo = 200; |
| assert.strictEqual(obj.foo, 200); |
| assert.strictEqual(prop.mock.accesses.length, 3); |
| assert.strictEqual(prop.mock.accesses[1].type, 'set'); |
| assert.strictEqual(prop.mock.accesses[1].value, 200); |
| assert.strictEqual(prop.mock.accesses[2].type, 'get'); |
| assert.strictEqual(prop.mock.accesses[2].value, 200); |
| |
| obj.foo = 300; |
| assert.strictEqual(obj.foo, 300); |
| assert.strictEqual(prop.mock.accessCount(), 5); |
| assert.strictEqual(prop.mock.accesses[3].type, 'set'); |
| assert.strictEqual(prop.mock.accesses[3].value, 300); |
| assert.strictEqual(prop.mock.accesses[4].type, 'get'); |
| assert.strictEqual(prop.mock.accesses[4].value, 300); |
| |
| prop.mock.resetAccesses(); |
| assert.strictEqual(prop.mock.accessCount(), 0); |
| |
| obj.foo = 500; |
| assert.strictEqual(obj.foo, 500); |
| assert.strictEqual(prop.mock.accessCount(), 2); |
| assert.strictEqual(prop.mock.accesses[0].type, 'set'); |
| assert.strictEqual(prop.mock.accesses[1].type, 'get'); |
| |
| prop.mock.resetAccesses(); |
| assert.strictEqual(prop.mock.accessCount(), 0); |
| |
| assert.strictEqual(obj.foo, 500); |
| assert.strictEqual(prop.mock.accessCount(), 1); |
| assert.strictEqual(prop.mock.accesses[0].type, 'get'); |
| |
| prop.mock.restore(); |
| assert.strictEqual(obj.foo, 42); |
| }); |
| |
| test('spies on a property without providing a value', (t) => { |
| const obj = { foo: 123 }; |
| const prop = t.mock.property(obj, 'foo'); |
| |
| assert.strictEqual(obj.foo, 123); |
| assert.strictEqual(prop.mock.accessCount(), 1); |
| assert.strictEqual(prop.mock.accesses[0].type, 'get'); |
| assert.strictEqual(prop.mock.accesses[0].value, 123); |
| |
| obj.foo = 456; |
| assert.strictEqual(obj.foo, 456); |
| assert.strictEqual(prop.mock.accessCount(), 3); |
| assert.strictEqual(prop.mock.accesses[1].type, 'set'); |
| assert.strictEqual(prop.mock.accesses[1].value, 456); |
| assert.strictEqual(prop.mock.accesses[2].type, 'get'); |
| assert.strictEqual(prop.mock.accesses[2].value, 456); |
| |
| prop.mock.restore(); |
| assert.strictEqual(obj.foo, 123); |
| }); |
| |
| test('spies on a symbol property', (t) => { |
| const symbol = Symbol('foo'); |
| const obj = { [symbol]: 123 }; |
| const prop = t.mock.property(obj, symbol, 456); |
| |
| assert.strictEqual(obj[symbol], 456); |
| assert.strictEqual(prop.mock.accessCount(), 1); |
| |
| obj[symbol] = 789; |
| assert.strictEqual(obj[symbol], 789); |
| assert.strictEqual(prop.mock.accessCount(), 3); |
| assert.strictEqual(prop.mock.accesses[1].type, 'set'); |
| assert.strictEqual(prop.mock.accesses[2].type, 'get'); |
| |
| prop.mock.restore(); |
| assert.strictEqual(obj[symbol], 123); |
| }); |
| |
| test('changes mocked property value dynamically', (t) => { |
| const obj = { foo: 1 }; |
| |
| const prop = t.mock.property(obj, 'foo', 2); |
| assert.strictEqual(obj.foo, 2); |
| assert.strictEqual(prop.mock.accessCount(), 1); |
| |
| prop.mock.mockImplementation(99); |
| assert.strictEqual(obj.foo, 99); |
| assert.strictEqual(prop.mock.accessCount(), 3); |
| |
| prop.mock.mockImplementationOnce(42); |
| assert.strictEqual(obj.foo, 42); |
| assert.strictEqual(obj.foo, 99); |
| assert.strictEqual(prop.mock.accessCount(), 5); |
| |
| assert.throws(() => { |
| prop.mock.mockImplementationOnce(55, 4); |
| }, /The value of "onAccess" is out of range\. It must be >= 5/); |
| |
| prop.mock.mockImplementationOnce(100, 5); |
| prop.mock.mockImplementationOnce(200, 6); |
| assert.strictEqual(obj.foo, 100); |
| assert.strictEqual(obj.foo, 200); |
| assert.strictEqual(obj.foo, 99); |
| assert.strictEqual(prop.mock.accessCount(), 8); |
| |
| prop.mock.mockImplementationOnce(555, 10); |
| assert.strictEqual(obj.foo, 99); |
| assert.strictEqual(obj.foo, 99); |
| assert.strictEqual(obj.foo, 555); |
| |
| prop.mock.mockImplementation(undefined); |
| assert.strictEqual(obj.foo, undefined); |
| }); |
| |
| test('mocks property value to undefined', (t) => { |
| const obj = { foo: 123 }; |
| const prop = t.mock.property(obj, 'foo', undefined); |
| |
| assert.strictEqual(obj.foo, undefined); |
| assert.strictEqual(prop.mock.accessCount(), 1); |
| assert.strictEqual(prop.mock.accesses[0].type, 'get'); |
| assert.strictEqual(prop.mock.accesses[0].value, undefined); |
| |
| prop.mock.restore(); |
| assert.strictEqual(obj.foo, 123); |
| }); |
| |
| test('resetAccesses does not affect property value', (t) => { |
| const obj = { foo: 1 }; |
| const prop = t.mock.property(obj, 'foo', 2); |
| |
| obj.foo = 5; |
| assert.strictEqual(obj.foo, 5); |
| assert.strictEqual(prop.mock.accessCount(), 2); |
| |
| prop.mock.resetAccesses(); |
| assert.strictEqual(obj.foo, 5); |
| assert.strictEqual(prop.mock.accessCount(), 1); |
| assert.strictEqual(prop.mock.accesses[0].type, 'get'); |
| }); |
| |
| test('restores original property value', (t) => { |
| const obj = { |
| foo: 10, |
| }; |
| |
| const prop = t.mock.property(obj, 'foo', 20); |
| assert.strictEqual(obj.foo, 20); |
| |
| prop.mock.restore(); |
| assert.strictEqual(obj.foo, 10); |
| }); |
| |
| test('throws if setting a non-writable property', (t) => { |
| const obj = {}; |
| Object.defineProperty(obj, 'bar', { |
| value: 1, |
| writable: false, |
| configurable: true, |
| enumerable: true, |
| }); |
| |
| t.mock.property(obj, 'bar', 2); |
| assert.strictEqual(obj.bar, 2); |
| |
| assert.throws(() => { obj.bar = 3; }, { code: 'ERR_INVALID_ARG_VALUE' }); |
| }); |
| |
| test('throws if property does not exist', (t) => { |
| assert.throws(() => { |
| t.mock.property({}, 'doesNotExist', 1); |
| }, { code: 'ERR_INVALID_ARG_VALUE' }); |
| }); |
| |
| test('throws if object is null', (t) => { |
| assert.throws(() => { |
| t.mock.property(null, 'foo', 1); |
| }, { code: 'ERR_INVALID_ARG_TYPE' }); |
| }); |
| |
| test('local property mocks are auto restored after the test finishes', async (t) => { |
| const obj = { foo: 111, bar: 222 }; |
| |
| assert.strictEqual(obj.foo, 111); |
| assert.strictEqual(obj.bar, 222); |
| |
| t.mock.property(obj, 'foo', 888); |
| |
| assert.strictEqual(obj.foo, 888); |
| assert.strictEqual(obj.bar, 222); |
| |
| t.beforeEach(() => { |
| assert.strictEqual(obj.foo, 888); |
| assert.strictEqual(obj.bar, 222); |
| }); |
| |
| t.afterEach(() => { |
| assert.strictEqual(obj.foo, 888); |
| assert.strictEqual(obj.bar, 999); |
| }); |
| |
| await t.test('creates property mocks that are auto restored', (t) => { |
| t.mock.property(obj, 'bar', 999); |
| |
| assert.strictEqual(obj.foo, 888); |
| assert.strictEqual(obj.bar, 999); |
| }); |
| |
| assert.strictEqual(obj.foo, 888); |
| assert.strictEqual(obj.bar, 222); |
| }); |