Skip to content

Commit

Permalink
test_runner: add getter and setter to MockTracker
Browse files Browse the repository at this point in the history
This commit allows tests in test runner to use the
`getter` and `setter` methods as "syntax sugar" for
`MockTracker.method` with the `options.getter` or
`options.setter` set to true in the options.

Refs: #45326 (comment)
  • Loading branch information
fossamagna committed Nov 19, 2022
1 parent 6f9175d commit 8e13ae4
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 0 deletions.
21 changes: 21 additions & 0 deletions doc/api/test.md
Expand Up @@ -941,6 +941,17 @@ test('mocks a counting function', (t) => {
});
```

they are sorted in alphabetical order.

### `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 +1032,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 +1377,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
58 changes: 58 additions & 0 deletions lib/internal/test_runner/mock.js
Expand Up @@ -202,6 +202,64 @@ class MockTracker {
return mock;
}

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

validateObject(options, 'options');

const { getter = true } = options;

validateBoolean(getter, 'options.getter');

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

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

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

validateObject(options, 'options');

const { setter = true } = options;

validateBoolean(setter, 'options.setter');

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

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

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);
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'/);
});

0 comments on commit 8e13ae4

Please sign in to comment.