Skip to content

Commit cde2736

Browse files
committed
test_runner: add getter and setter to MockTracker
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)
1 parent 6f9175d commit cde2736

File tree

3 files changed

+164
-0
lines changed

3 files changed

+164
-0
lines changed

doc/api/test.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,15 @@ test('mocks a counting function', (t) => {
940940
assert.strictEqual(fn(), 6);
941941
});
942942
```
943+
they are sorted in alphabetical order.
944+
### `mock.getter(object, methodName[, implementation][, options])`
945+
946+
<!-- YAML
947+
added: REPLACEME
948+
-->
949+
950+
This function is syntax sugar for [`MockTracker.method`][] with `options.getter`
951+
set to `true`.
943952

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

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

1033+
### `mock.setter(object, methodName[, implementation][, options])`
1034+
1035+
<!-- YAML
1036+
added: REPLACEME
1037+
-->
1038+
1039+
This function is syntax sugar for [`MockTracker.method`][] with `options.setter`
1040+
set to `true`.
1041+
10241042
## Class: `TapStream`
10251043

10261044
<!-- YAML
@@ -1357,6 +1375,7 @@ added:
13571375
[`--test-only`]: cli.md#--test-only
13581376
[`--test`]: cli.md#--test
13591377
[`MockFunctionContext`]: #class-mockfunctioncontext
1378+
[`MockTracker.method`]: #mockmethodobject-methodname-implementation-options
13601379
[`MockTracker`]: #class-mocktracker
13611380
[`SuiteContext`]: #class-suitecontext
13621381
[`TestContext`]: #class-testcontext

lib/internal/test_runner/mock.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,64 @@ class MockTracker {
202202
return mock;
203203
}
204204

205+
getter(
206+
object,
207+
methodName,
208+
implementation = kDefaultFunction,
209+
options = kEmptyObject
210+
) {
211+
if (implementation !== null && typeof implementation === 'object') {
212+
options = implementation;
213+
implementation = kDefaultFunction;
214+
}
215+
216+
validateObject(options, 'options');
217+
218+
const { getter = true } = options;
219+
220+
validateBoolean(getter, 'options.getter');
221+
222+
if (getter === false) {
223+
throw new ERR_INVALID_ARG_VALUE(
224+
'options.getter', getter, "cannot be 'false'"
225+
);
226+
}
227+
228+
return this.method(object, methodName, implementation, {
229+
...options,
230+
getter: true
231+
});
232+
}
233+
234+
setter(
235+
object,
236+
methodName,
237+
implementation = kDefaultFunction,
238+
options = kEmptyObject
239+
) {
240+
if (implementation !== null && typeof implementation === 'object') {
241+
options = implementation;
242+
implementation = kDefaultFunction;
243+
}
244+
245+
validateObject(options, 'options');
246+
247+
const { setter = true } = options;
248+
249+
validateBoolean(setter, 'options.setter');
250+
251+
if (setter === false) {
252+
throw new ERR_INVALID_ARG_VALUE(
253+
'options.setter', setter, "cannot be 'false'"
254+
);
255+
}
256+
257+
return this.method(object, methodName, implementation, {
258+
...options,
259+
setter: true
260+
});
261+
}
262+
205263
reset() {
206264
this.restoreAll();
207265
this.#mocks = [];

test/parallel/test-runner-mocking.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,69 @@ test('mocks a setter', (t) => {
534534
assert.strictEqual(obj.prop, 65);
535535
});
536536

537+
test('mocks a getter with syntax sugar', (t) => {
538+
const obj = {
539+
prop: 5,
540+
get method() {
541+
return this.prop;
542+
},
543+
};
544+
545+
function mockMethod() {
546+
return this.prop - 1;
547+
}
548+
const getter = t.mock.getter(obj, 'method', mockMethod);
549+
assert.strictEqual(getter.mock.calls.length, 0);
550+
assert.strictEqual(obj.method, 4);
551+
552+
const call = getter.mock.calls[0];
553+
554+
assert.deepStrictEqual(call.arguments, []);
555+
assert.strictEqual(call.result, 4);
556+
assert.strictEqual(call.target, undefined);
557+
assert.strictEqual(call.this, obj);
558+
559+
assert.strictEqual(getter.mock.restore(), undefined);
560+
assert.strictEqual(obj.method, 5);
561+
});
562+
563+
test('mocks a setter with syntax sugar', (t) => {
564+
const obj = {
565+
prop: 100,
566+
// eslint-disable-next-line accessor-pairs
567+
set method(val) {
568+
this.prop = val;
569+
},
570+
};
571+
572+
function mockMethod(val) {
573+
this.prop = -val;
574+
}
575+
576+
assert.strictEqual(obj.prop, 100);
577+
obj.method = 88;
578+
assert.strictEqual(obj.prop, 88);
579+
580+
const setter = t.mock.setter(obj, 'method', mockMethod);
581+
582+
assert.strictEqual(setter.mock.calls.length, 0);
583+
obj.method = 77;
584+
assert.strictEqual(obj.prop, -77);
585+
assert.strictEqual(setter.mock.calls.length, 1);
586+
587+
const call = setter.mock.calls[0];
588+
589+
assert.deepStrictEqual(call.arguments, [77]);
590+
assert.strictEqual(call.result, undefined);
591+
assert.strictEqual(call.target, undefined);
592+
assert.strictEqual(call.this, obj);
593+
594+
assert.strictEqual(setter.mock.restore(), undefined);
595+
assert.strictEqual(obj.prop, -77);
596+
obj.method = 65;
597+
assert.strictEqual(obj.prop, 65);
598+
});
599+
537600
test('mocked functions match name and length', (t) => {
538601
function getNameAndLength(fn) {
539602
return {
@@ -799,3 +862,27 @@ test('spies on a class prototype method', (t) => {
799862
assert.strictEqual(call.target, undefined);
800863
assert.strictEqual(call.this, instance);
801864
});
865+
866+
test('getter() fails if getter options set to false', (t) => {
867+
assert.throws(() => {
868+
t.mock.getter({}, 'method', { getter: false });
869+
}, /The property 'options\.getter' cannot be 'false'/);
870+
});
871+
872+
test('setter() fails if setter options set to false', (t) => {
873+
assert.throws(() => {
874+
t.mock.setter({}, 'method', { setter: false });
875+
}, /The property 'options\.setter' cannot be 'false'/);
876+
});
877+
878+
test('getter() fails if setter options is true', (t) => {
879+
assert.throws(() => {
880+
t.mock.getter({}, 'method', { setter: true });
881+
}, /The property 'options\.setter' cannot be used with 'options\.getter'/);
882+
});
883+
884+
test('setter() fails if getter options is true', (t) => {
885+
assert.throws(() => {
886+
t.mock.setter({}, 'method', { getter: true });
887+
}, /The property 'options\.setter' cannot be used with 'options\.getter'/);
888+
});

0 commit comments

Comments
 (0)