Skip to content

Commit

Permalink
test_runner: ensure that mock.method works with class inheritance
Browse files Browse the repository at this point in the history
  • Loading branch information
ErickWendel committed Nov 30, 2022
1 parent 78d7141 commit 66939bc
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 9 deletions.
80 changes: 71 additions & 9 deletions lib/internal/test_runner/mock.js
Expand Up @@ -174,19 +174,27 @@ class MockTracker {

return original;
}
let descriptor = ObjectGetOwnPropertyDescriptor(object, methodName);
let original = getOriginalObject(descriptor);

// classes instances
if (typeof original !== 'function') {
descriptor = ObjectGetOwnPropertyDescriptor(
ObjectGetPrototypeOf(object),
methodName
);
function findMethodOnPrototypeChain(instance, method, isDescriptor = false) {
const original = isDescriptor ?
instance :
ObjectGetOwnPropertyDescriptor(instance, method);

if (original && !isDescriptor) {
return original;
}

original = getOriginalObject(descriptor);
const proto = ObjectGetPrototypeOf(instance);
const desc = ObjectGetOwnPropertyDescriptor(proto, method);
if (!desc && proto.name) {
return findMethodOnPrototypeChain(proto, method, true);
}
return desc;
}

const descriptor = findMethodOnPrototypeChain(object, methodName);
const original = getOriginalObject(descriptor);

if (typeof original !== 'function') {
throw new ERR_INVALID_ARG_VALUE(
'methodName', original, 'must be a method'
Expand Down Expand Up @@ -220,6 +228,60 @@ class MockTracker {
return mock;
}

getter(
object,
methodName,
implementation = kDefaultFunction,
options = kEmptyObject
) {
if (implementation !== null && typeof implementation === 'object') {
options = implementation;
implementation = kDefaultFunction;
} else {
validateObject(options, 'options');
}

const { getter = true } = options;

if (getter === false) {
throw new ERR_INVALID_ARG_VALUE(
'options.getter', getter, 'cannot be false'
);
}

return this.method(object, methodName, implementation, {
...options,
getter,
});
}

setter(
object,
methodName,
implementation = kDefaultFunction,
options = kEmptyObject
) {
if (implementation !== null && typeof implementation === 'object') {
options = implementation;
implementation = kDefaultFunction;
} else {
validateObject(options, 'options');
}

const { setter = true } = options;

if (setter === false) {
throw new ERR_INVALID_ARG_VALUE(
'options.setter', setter, 'cannot be false'
);
}

return this.method(object, methodName, implementation, {
...options,
setter,
});
}

reset() {
this.restoreAll();
this.#mocks = [];
Expand Down
44 changes: 44 additions & 0 deletions test/parallel/test-runner-mocking.js
Expand Up @@ -318,6 +318,7 @@ test('spy functions can be bound', (t) => {
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) {
Expand Down Expand Up @@ -345,6 +346,13 @@ test('mocks prototype methods on an instance', async (t) => {
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);
Expand Down Expand Up @@ -381,6 +389,42 @@ test('spies on async static class methods', async (t) => {
assert.strictEqual(Runner.someTask.mock, undefined);
});

test('given null to a mock.method it throws a invalid argument error', (t) => {
assert.throws(() => t.mock.method(null, {}), /ERR_INVALID_ARG_TYPE/);
});

test('spy functions can be used on classes inheritance', (t) => {
class A {
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('mocked functions report thrown errors', (t) => {
const testError = new Error('test error');
const fn = t.mock.fn(() => {
Expand Down

0 comments on commit 66939bc

Please sign in to comment.