Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test_runner: add getter and setter to MockTracker #45506

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 19 additions & 0 deletions doc/api/test.md
Expand Up @@ -941,6 +941,15 @@ test('mocks a counting function', (t) => {
});
```

### `mock.getter(object, methodName[, implementation][, options])`

<!-- YAML
added: REPLACEME
-->

This function is syntax sugar for [`MockTracker.method`][] with `options.getter`
set to `true`.

### `mock.method(object, methodName[, implementation][, options])`

<!-- YAML
Expand Down Expand Up @@ -1021,6 +1030,15 @@ This function restores the default behavior of all mocks that were previously
created by this `MockTracker`. Unlike `mock.reset()`, `mock.restoreAll()` does
not disassociate the mocks from the `MockTracker` instance.

### `mock.setter(object, methodName[, implementation][, options])`

<!-- YAML
added: REPLACEME
-->

This function is syntax sugar for [`MockTracker.method`][] with `options.setter`
set to `true`.

## Class: `TapStream`

<!-- YAML
Expand Down Expand Up @@ -1357,6 +1375,7 @@ added:
[`--test-only`]: cli.md#--test-only
[`--test`]: cli.md#--test
[`MockFunctionContext`]: #class-mockfunctioncontext
[`MockTracker.method`]: #mockmethodobject-methodname-implementation-options
[`MockTracker`]: #class-mocktracker
[`SuiteContext`]: #class-suitecontext
[`TestContext`]: #class-testcontext
Expand Down
54 changes: 54 additions & 0 deletions lib/internal/test_runner/mock.js
Expand Up @@ -202,6 +202,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;
aduh95 marked this conversation as resolved.
Show resolved Hide resolved

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

return this.method(object, methodName, implementation, {
...options,
aduh95 marked this conversation as resolved.
Show resolved Hide resolved
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;
aduh95 marked this conversation as resolved.
Show resolved Hide resolved

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
87 changes: 87 additions & 0 deletions test/parallel/test-runner-mocking.js
Expand Up @@ -534,6 +534,69 @@ test('mocks a setter', (t) => {
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add tests that try to set getter and setter to false. Tests that throw when trying to set setter: true when calling getter() and vice versa would be nice to.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cjihrig Thank you for your suggestions.
If getter cannot be set to a different value, I think that validation do not work for options in method().
Do we implement validation for options in getter() and setter() ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If getter cannot be set to a different value, I think that validation do not work for options in method().

There is validation, but if you set the getter option to false, it would pass validation, but the getter() method would not make sense.

Do we implement validation for options in getter() and setter() ?

I would be OK with validating the getter or setter option to ensure that they are not set to any value besides true and deferring all other validation to method().

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 {
Expand Down Expand Up @@ -799,3 +862,27 @@ test('spies on a class prototype method', (t) => {
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'/);
});