Skip to content

Commit

Permalink
feat: 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: nodejs/node#45326 (comment)
PR-URL: nodejs/node#45506
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
(cherry picked from commit afed1afa55962211b6b56c2068d520b4d8a08888)
  • Loading branch information
fossamagna authored and MoLow committed Feb 2, 2023
1 parent 5ba2500 commit b942f93
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 2 deletions.
19 changes: 19 additions & 0 deletions README.md
Expand Up @@ -866,6 +866,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 @@ -943,6 +952,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 @@ -1153,6 +1171,7 @@ The name of the suite.
[`AbortSignal`]: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal
[TAP]: https://testanything.org/
[`MockFunctionContext`]: #class-mockfunctioncontext
[`MockTracker.method`]: #mockmethodobject-methodname-implementation-options
[`MockTracker`]: #class-mocktracke
[`SuiteContext`]: #class-suitecontext
[`TestContext`]: #class-testcontext
Expand Down
56 changes: 55 additions & 1 deletion lib/internal/test_runner/mock.js
@@ -1,4 +1,4 @@
// https://github.com/nodejs/node/blob/7c6682957b3c5f86d0616cebc0ad09cc2a1fd50d/lib/internal/test_runner/mock.js
// https://github.com/nodejs/node/blob/afed1afa55962211b6b56c2068d520b4d8a08888/lib/internal/test_runner/mock.js
'use strict'
const {
ArrayPrototypePush,
Expand Down Expand Up @@ -204,6 +204,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
89 changes: 88 additions & 1 deletion test/parallel/test-runner-mocking.js
@@ -1,4 +1,4 @@
// https://github.com/nodejs/node/blob/7c6682957b3c5f86d0616cebc0ad09cc2a1fd50d/test/parallel/test-runner-mocking.js
// https://github.com/nodejs/node/blob/afed1afa55962211b6b56c2068d520b4d8a08888/test/parallel/test-runner-mocking.js
'use strict'
const common = require('../common')
const assert = require('node:assert')
Expand Down Expand Up @@ -535,6 +535,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 @@ -800,3 +863,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 b942f93

Please sign in to comment.