test_runner: support object property mocking
PR-URL: https://github.com/nodejs/node/pull/58438 Fixes: https://github.com/nodejs/node/issues/58322 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
This commit is contained in:
parent
3aaa2ebe19
commit
905a722df3
119
doc/api/test.md
119
doc/api/test.md
@ -2048,6 +2048,87 @@ added:
|
|||||||
|
|
||||||
Resets the implementation of the mock module.
|
Resets the implementation of the mock module.
|
||||||
|
|
||||||
|
## Class: `MockPropertyContext`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
The `MockPropertyContext` class is used to inspect or manipulate the behavior
|
||||||
|
of property mocks created via the [`MockTracker`][] APIs.
|
||||||
|
|
||||||
|
### `ctx.accesses`
|
||||||
|
|
||||||
|
* {Array}
|
||||||
|
|
||||||
|
A getter that returns a copy of the internal array used to track accesses (get/set) to
|
||||||
|
the mocked property. Each entry in the array is an object with the following properties:
|
||||||
|
|
||||||
|
* `type` {string} Either `'get'` or `'set'`, indicating the type of access.
|
||||||
|
* `value` {any} The value that was read (for `'get'`) or written (for `'set'`).
|
||||||
|
* `stack` {Error} An `Error` object whose stack can be used to determine the
|
||||||
|
callsite of the mocked function invocation.
|
||||||
|
|
||||||
|
### `ctx.accessCount()`
|
||||||
|
|
||||||
|
* Returns: {integer} The number of times that the property was accessed (read or written).
|
||||||
|
|
||||||
|
This function returns the number of times that the property was accessed.
|
||||||
|
This function is more efficient than checking `ctx.accesses.length` because
|
||||||
|
`ctx.accesses` is a getter that creates a copy of the internal access tracking array.
|
||||||
|
|
||||||
|
### `ctx.mockImplementation(value)`
|
||||||
|
|
||||||
|
* `value` {any} The new value to be set as the mocked property value.
|
||||||
|
|
||||||
|
This function is used to change the value returned by the mocked property getter.
|
||||||
|
|
||||||
|
### `ctx.mockImplementationOnce(value[, onAccess])`
|
||||||
|
|
||||||
|
* `value` {any} The value to be used as the mock's
|
||||||
|
implementation for the invocation number specified by `onAccess`.
|
||||||
|
* `onAccess` {integer} The invocation number that will use `value`. If
|
||||||
|
the specified invocation has already occurred then an exception is thrown.
|
||||||
|
**Default:** The number of the next invocation.
|
||||||
|
|
||||||
|
This function is used to change the behavior of an existing mock for a single
|
||||||
|
invocation. Once invocation `onAccess` has occurred, the mock will revert to
|
||||||
|
whatever behavior it would have used had `mockImplementationOnce()` not been
|
||||||
|
called.
|
||||||
|
|
||||||
|
The following example creates a mock function using `t.mock.property()`, calls the
|
||||||
|
mock property, changes the mock implementation to a different value for the
|
||||||
|
next invocation, and then resumes its previous behavior.
|
||||||
|
|
||||||
|
```js
|
||||||
|
test('changes a mock behavior once', (t) => {
|
||||||
|
const obj = { foo: 1 };
|
||||||
|
|
||||||
|
const prop = t.mock.property(obj, 'foo', 5);
|
||||||
|
|
||||||
|
assert.strictEqual(obj.foo, 5);
|
||||||
|
prop.mock.mockImplementationOnce(25);
|
||||||
|
assert.strictEqual(obj.foo, 25);
|
||||||
|
assert.strictEqual(obj.foo, 5);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Caveat
|
||||||
|
|
||||||
|
For consistency with the rest of the mocking API, this function treats both property gets and sets
|
||||||
|
as accesses. If a property set occurs at the same access index, the "once" value will be consumed
|
||||||
|
by the set operation, and the mocked property value will be changed to the "once" value. This may
|
||||||
|
lead to unexpected behavior if you intend the "once" value to only be used for a get operation.
|
||||||
|
|
||||||
|
### `ctx.resetAccesses()`
|
||||||
|
|
||||||
|
Resets the access history of the mocked property.
|
||||||
|
|
||||||
|
### `ctx.restore()`
|
||||||
|
|
||||||
|
Resets the implementation of the mock property to its original behavior. The
|
||||||
|
mock can still be used after calling this function.
|
||||||
|
|
||||||
## Class: `MockTracker`
|
## Class: `MockTracker`
|
||||||
|
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
@ -2252,6 +2333,43 @@ test('mocks a builtin module in both module systems', async (t) => {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `mock.property(object, propertyName[, value])`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `object` {Object} The object whose value is being mocked.
|
||||||
|
* `propertyName` {string|symbol} The identifier of the property on `object` to mock.
|
||||||
|
* `value` {any} An optional value used as the mock value
|
||||||
|
for `object[propertyName]`. **Default:** The original property value.
|
||||||
|
* Returns: {Proxy} A proxy to the mocked object. The mocked object contains a
|
||||||
|
special `mock` property, which is an instance of [`MockPropertyContext`][], and
|
||||||
|
can be used for inspecting and changing the behavior of the mocked property.
|
||||||
|
|
||||||
|
Creates a mock for a property value on an object. This allows you to track and control access to a specific property,
|
||||||
|
including how many times it is read (getter) or written (setter), and to restore the original value after mocking.
|
||||||
|
|
||||||
|
```js
|
||||||
|
test('mocks a property value', (t) => {
|
||||||
|
const obj = { foo: 42 };
|
||||||
|
const prop = t.mock.property(obj, 'foo', 100);
|
||||||
|
|
||||||
|
assert.strictEqual(obj.foo, 100);
|
||||||
|
assert.strictEqual(prop.mock.accessCount(), 1);
|
||||||
|
assert.strictEqual(prop.mock.accesses[0].type, 'get');
|
||||||
|
assert.strictEqual(prop.mock.accesses[0].value, 100);
|
||||||
|
|
||||||
|
obj.foo = 200;
|
||||||
|
assert.strictEqual(prop.mock.accessCount(), 2);
|
||||||
|
assert.strictEqual(prop.mock.accesses[1].type, 'set');
|
||||||
|
assert.strictEqual(prop.mock.accesses[1].value, 200);
|
||||||
|
|
||||||
|
prop.mock.restore();
|
||||||
|
assert.strictEqual(obj.foo, 42);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
### `mock.reset()`
|
### `mock.reset()`
|
||||||
|
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
@ -3790,6 +3908,7 @@ Can be used to abort test subtasks when the test has been aborted.
|
|||||||
[`--test-update-snapshots`]: cli.md#--test-update-snapshots
|
[`--test-update-snapshots`]: cli.md#--test-update-snapshots
|
||||||
[`--test`]: cli.md#--test
|
[`--test`]: cli.md#--test
|
||||||
[`MockFunctionContext`]: #class-mockfunctioncontext
|
[`MockFunctionContext`]: #class-mockfunctioncontext
|
||||||
|
[`MockPropertyContext`]: #class-mockpropertycontext
|
||||||
[`MockTimers`]: #class-mocktimers
|
[`MockTimers`]: #class-mocktimers
|
||||||
[`MockTracker.method`]: #mockmethodobject-methodname-implementation-options
|
[`MockTracker.method`]: #mockmethodobject-methodname-implementation-options
|
||||||
[`MockTracker`]: #class-mocktracker
|
[`MockTracker`]: #class-mocktracker
|
||||||
|
@ -284,6 +284,134 @@ class MockModuleContext {
|
|||||||
|
|
||||||
const { restore: restoreModule } = MockModuleContext.prototype;
|
const { restore: restoreModule } = MockModuleContext.prototype;
|
||||||
|
|
||||||
|
class MockPropertyContext {
|
||||||
|
#object;
|
||||||
|
#propertyName;
|
||||||
|
#value;
|
||||||
|
#originalValue;
|
||||||
|
#descriptor;
|
||||||
|
#accesses;
|
||||||
|
#onceValues;
|
||||||
|
|
||||||
|
constructor(object, propertyName, value) {
|
||||||
|
this.#onceValues = new SafeMap();
|
||||||
|
this.#accesses = [];
|
||||||
|
this.#object = object;
|
||||||
|
this.#propertyName = propertyName;
|
||||||
|
this.#originalValue = object[propertyName];
|
||||||
|
this.#value = arguments.length > 2 ? value : this.#originalValue;
|
||||||
|
this.#descriptor = ObjectGetOwnPropertyDescriptor(object, propertyName);
|
||||||
|
if (!this.#descriptor) {
|
||||||
|
throw new ERR_INVALID_ARG_VALUE(
|
||||||
|
'propertyName', propertyName, 'is not a property of the object',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { configurable, enumerable } = this.#descriptor;
|
||||||
|
ObjectDefineProperty(object, propertyName, {
|
||||||
|
__proto__: null,
|
||||||
|
configurable,
|
||||||
|
enumerable,
|
||||||
|
get: () => {
|
||||||
|
const nextValue = this.#getAccessValue(this.#value);
|
||||||
|
const access = {
|
||||||
|
__proto__: null,
|
||||||
|
type: 'get',
|
||||||
|
value: nextValue,
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
stack: new Error(),
|
||||||
|
};
|
||||||
|
ArrayPrototypePush(this.#accesses, access);
|
||||||
|
return nextValue;
|
||||||
|
},
|
||||||
|
set: this.mockImplementation.bind(this),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an array of recorded accesses (get/set) to the property.
|
||||||
|
* @returns {Array} An array of access records.
|
||||||
|
*/
|
||||||
|
get accesses() {
|
||||||
|
return ArrayPrototypeSlice(this.#accesses, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the number of times the property was accessed (get or set).
|
||||||
|
* @returns {number} The total number of accesses.
|
||||||
|
*/
|
||||||
|
accessCount() {
|
||||||
|
return this.#accesses.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a new value for the property.
|
||||||
|
* @param {any} value - The new value to be set.
|
||||||
|
* @throws {Error} If the property is not writable.
|
||||||
|
*/
|
||||||
|
mockImplementation(value) {
|
||||||
|
if (!this.#descriptor.writable) {
|
||||||
|
throw new ERR_INVALID_ARG_VALUE(
|
||||||
|
'propertyName', this.#propertyName, 'cannot be set',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const nextValue = this.#getAccessValue(value);
|
||||||
|
const access = {
|
||||||
|
__proto__: null,
|
||||||
|
type: 'set',
|
||||||
|
value: nextValue,
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
stack: new Error(),
|
||||||
|
};
|
||||||
|
ArrayPrototypePush(this.#accesses, access);
|
||||||
|
this.#value = nextValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
#getAccessValue(value) {
|
||||||
|
const accessIndex = this.#accesses.length;
|
||||||
|
let accessValue;
|
||||||
|
if (this.#onceValues.has(accessIndex)) {
|
||||||
|
accessValue = this.#onceValues.get(accessIndex);
|
||||||
|
this.#onceValues.delete(accessIndex);
|
||||||
|
} else {
|
||||||
|
accessValue = value;
|
||||||
|
}
|
||||||
|
return accessValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a value to be used only for the next access (get or set), or a specific access index.
|
||||||
|
* @param {any} value - The value to be used once.
|
||||||
|
* @param {number} [onAccess] - The access index to be replaced.
|
||||||
|
*/
|
||||||
|
mockImplementationOnce(value, onAccess) {
|
||||||
|
const nextAccess = this.#accesses.length;
|
||||||
|
const accessIndex = onAccess ?? nextAccess;
|
||||||
|
validateInteger(accessIndex, 'onAccess', nextAccess);
|
||||||
|
this.#onceValues.set(accessIndex, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the recorded accesses to the property.
|
||||||
|
*/
|
||||||
|
resetAccesses() {
|
||||||
|
this.#accesses = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restores the original value of the property that was mocked.
|
||||||
|
*/
|
||||||
|
restore() {
|
||||||
|
ObjectDefineProperty(this.#object, this.#propertyName, {
|
||||||
|
__proto__: null,
|
||||||
|
...this.#descriptor,
|
||||||
|
value: this.#originalValue,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { restore: restoreProperty } = MockPropertyContext.prototype;
|
||||||
|
|
||||||
class MockTracker {
|
class MockTracker {
|
||||||
#mocks = [];
|
#mocks = [];
|
||||||
#timers;
|
#timers;
|
||||||
@ -573,6 +701,41 @@ class MockTracker {
|
|||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a property tracker for a specified object.
|
||||||
|
* @param {(object)} object - The object whose value is being tracked.
|
||||||
|
* @param {string} propertyName - The identifier of the property on object to be tracked.
|
||||||
|
* @param {any} value - An optional replacement value used as the mock value for object[valueName].
|
||||||
|
* @returns {ProxyConstructor} The mock property tracker.
|
||||||
|
*/
|
||||||
|
property(
|
||||||
|
object,
|
||||||
|
propertyName,
|
||||||
|
value,
|
||||||
|
) {
|
||||||
|
validateObject(object, 'object');
|
||||||
|
validateStringOrSymbol(propertyName, 'propertyName');
|
||||||
|
|
||||||
|
const ctx = arguments.length > 2 ?
|
||||||
|
new MockPropertyContext(object, propertyName, value) :
|
||||||
|
new MockPropertyContext(object, propertyName);
|
||||||
|
ArrayPrototypePush(this.#mocks, {
|
||||||
|
__proto__: null,
|
||||||
|
ctx,
|
||||||
|
restore: restoreProperty,
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Proxy(object, {
|
||||||
|
__proto__: null,
|
||||||
|
get(target, property, receiver) {
|
||||||
|
if (property === 'mock') {
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
return ReflectGet(target, property, receiver);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the mock tracker, restoring all mocks and clearing timers.
|
* Resets the mock tracker, restoring all mocks and clearing timers.
|
||||||
*/
|
*/
|
||||||
|
@ -391,7 +391,7 @@ test('spies on async static class methods', async (t) => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('given null to a mock.method it throws a invalid argument error', (t) => {
|
test('given null to a mock.method it throws an invalid argument error', (t) => {
|
||||||
assert.throws(() => t.mock.method(null, {}), { code: 'ERR_INVALID_ARG_TYPE' });
|
assert.throws(() => t.mock.method(null, {}), { code: 'ERR_INVALID_ARG_TYPE' });
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1054,3 +1054,221 @@ test('setter() fails if getter options is true', (t) => {
|
|||||||
t.mock.setter({}, 'method', { getter: true });
|
t.mock.setter({}, 'method', { getter: true });
|
||||||
}, /The property 'options\.setter' cannot be used with 'options\.getter'/);
|
}, /The property 'options\.setter' cannot be used with 'options\.getter'/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('spies on a property', (t) => {
|
||||||
|
const obj = { foo: 42 };
|
||||||
|
const prop = t.mock.property(obj, 'foo', 100);
|
||||||
|
|
||||||
|
assert.strictEqual(obj.foo, 100);
|
||||||
|
assert.strictEqual(prop.mock.accessCount(), 1);
|
||||||
|
assert.strictEqual(prop.mock.accesses[0].type, 'get');
|
||||||
|
assert.strictEqual(prop.mock.accesses[0].value, 100);
|
||||||
|
|
||||||
|
obj.foo = 200;
|
||||||
|
assert.strictEqual(obj.foo, 200);
|
||||||
|
assert.strictEqual(prop.mock.accesses.length, 3);
|
||||||
|
assert.strictEqual(prop.mock.accesses[1].type, 'set');
|
||||||
|
assert.strictEqual(prop.mock.accesses[1].value, 200);
|
||||||
|
assert.strictEqual(prop.mock.accesses[2].type, 'get');
|
||||||
|
assert.strictEqual(prop.mock.accesses[2].value, 200);
|
||||||
|
|
||||||
|
obj.foo = 300;
|
||||||
|
assert.strictEqual(obj.foo, 300);
|
||||||
|
assert.strictEqual(prop.mock.accessCount(), 5);
|
||||||
|
assert.strictEqual(prop.mock.accesses[3].type, 'set');
|
||||||
|
assert.strictEqual(prop.mock.accesses[3].value, 300);
|
||||||
|
assert.strictEqual(prop.mock.accesses[4].type, 'get');
|
||||||
|
assert.strictEqual(prop.mock.accesses[4].value, 300);
|
||||||
|
|
||||||
|
prop.mock.resetAccesses();
|
||||||
|
assert.strictEqual(prop.mock.accessCount(), 0);
|
||||||
|
|
||||||
|
obj.foo = 500;
|
||||||
|
assert.strictEqual(obj.foo, 500);
|
||||||
|
assert.strictEqual(prop.mock.accessCount(), 2);
|
||||||
|
assert.strictEqual(prop.mock.accesses[0].type, 'set');
|
||||||
|
assert.strictEqual(prop.mock.accesses[1].type, 'get');
|
||||||
|
|
||||||
|
prop.mock.resetAccesses();
|
||||||
|
assert.strictEqual(prop.mock.accessCount(), 0);
|
||||||
|
|
||||||
|
assert.strictEqual(obj.foo, 500);
|
||||||
|
assert.strictEqual(prop.mock.accessCount(), 1);
|
||||||
|
assert.strictEqual(prop.mock.accesses[0].type, 'get');
|
||||||
|
|
||||||
|
prop.mock.restore();
|
||||||
|
assert.strictEqual(obj.foo, 42);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('spies on a property without providing a value', (t) => {
|
||||||
|
const obj = { foo: 123 };
|
||||||
|
const prop = t.mock.property(obj, 'foo');
|
||||||
|
|
||||||
|
assert.strictEqual(obj.foo, 123);
|
||||||
|
assert.strictEqual(prop.mock.accessCount(), 1);
|
||||||
|
assert.strictEqual(prop.mock.accesses[0].type, 'get');
|
||||||
|
assert.strictEqual(prop.mock.accesses[0].value, 123);
|
||||||
|
|
||||||
|
obj.foo = 456;
|
||||||
|
assert.strictEqual(obj.foo, 456);
|
||||||
|
assert.strictEqual(prop.mock.accessCount(), 3);
|
||||||
|
assert.strictEqual(prop.mock.accesses[1].type, 'set');
|
||||||
|
assert.strictEqual(prop.mock.accesses[1].value, 456);
|
||||||
|
assert.strictEqual(prop.mock.accesses[2].type, 'get');
|
||||||
|
assert.strictEqual(prop.mock.accesses[2].value, 456);
|
||||||
|
|
||||||
|
prop.mock.restore();
|
||||||
|
assert.strictEqual(obj.foo, 123);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('spies on a symbol property', (t) => {
|
||||||
|
const symbol = Symbol('foo');
|
||||||
|
const obj = { [symbol]: 123 };
|
||||||
|
const prop = t.mock.property(obj, symbol, 456);
|
||||||
|
|
||||||
|
assert.strictEqual(obj[symbol], 456);
|
||||||
|
assert.strictEqual(prop.mock.accessCount(), 1);
|
||||||
|
|
||||||
|
obj[symbol] = 789;
|
||||||
|
assert.strictEqual(obj[symbol], 789);
|
||||||
|
assert.strictEqual(prop.mock.accessCount(), 3);
|
||||||
|
assert.strictEqual(prop.mock.accesses[1].type, 'set');
|
||||||
|
assert.strictEqual(prop.mock.accesses[2].type, 'get');
|
||||||
|
|
||||||
|
prop.mock.restore();
|
||||||
|
assert.strictEqual(obj[symbol], 123);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('changes mocked property value dynamically', (t) => {
|
||||||
|
const obj = { foo: 1 };
|
||||||
|
|
||||||
|
const prop = t.mock.property(obj, 'foo', 2);
|
||||||
|
assert.strictEqual(obj.foo, 2);
|
||||||
|
assert.strictEqual(prop.mock.accessCount(), 1);
|
||||||
|
|
||||||
|
prop.mock.mockImplementation(99);
|
||||||
|
assert.strictEqual(obj.foo, 99);
|
||||||
|
assert.strictEqual(prop.mock.accessCount(), 3);
|
||||||
|
|
||||||
|
prop.mock.mockImplementationOnce(42);
|
||||||
|
assert.strictEqual(obj.foo, 42);
|
||||||
|
assert.strictEqual(obj.foo, 99);
|
||||||
|
assert.strictEqual(prop.mock.accessCount(), 5);
|
||||||
|
|
||||||
|
assert.throws(() => {
|
||||||
|
prop.mock.mockImplementationOnce(55, 4);
|
||||||
|
}, /The value of "onAccess" is out of range\. It must be >= 5/);
|
||||||
|
|
||||||
|
prop.mock.mockImplementationOnce(100, 5);
|
||||||
|
prop.mock.mockImplementationOnce(200, 6);
|
||||||
|
assert.strictEqual(obj.foo, 100);
|
||||||
|
assert.strictEqual(obj.foo, 200);
|
||||||
|
assert.strictEqual(obj.foo, 99);
|
||||||
|
assert.strictEqual(prop.mock.accessCount(), 8);
|
||||||
|
|
||||||
|
prop.mock.mockImplementationOnce(555, 10);
|
||||||
|
assert.strictEqual(obj.foo, 99);
|
||||||
|
assert.strictEqual(obj.foo, 99);
|
||||||
|
assert.strictEqual(obj.foo, 555);
|
||||||
|
|
||||||
|
prop.mock.mockImplementation(undefined);
|
||||||
|
assert.strictEqual(obj.foo, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('mocks property value to undefined', (t) => {
|
||||||
|
const obj = { foo: 123 };
|
||||||
|
const prop = t.mock.property(obj, 'foo', undefined);
|
||||||
|
|
||||||
|
assert.strictEqual(obj.foo, undefined);
|
||||||
|
assert.strictEqual(prop.mock.accessCount(), 1);
|
||||||
|
assert.strictEqual(prop.mock.accesses[0].type, 'get');
|
||||||
|
assert.strictEqual(prop.mock.accesses[0].value, undefined);
|
||||||
|
|
||||||
|
prop.mock.restore();
|
||||||
|
assert.strictEqual(obj.foo, 123);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('resetAccesses does not affect property value', (t) => {
|
||||||
|
const obj = { foo: 1 };
|
||||||
|
const prop = t.mock.property(obj, 'foo', 2);
|
||||||
|
|
||||||
|
obj.foo = 5;
|
||||||
|
assert.strictEqual(obj.foo, 5);
|
||||||
|
assert.strictEqual(prop.mock.accessCount(), 2);
|
||||||
|
|
||||||
|
prop.mock.resetAccesses();
|
||||||
|
assert.strictEqual(obj.foo, 5);
|
||||||
|
assert.strictEqual(prop.mock.accessCount(), 1);
|
||||||
|
assert.strictEqual(prop.mock.accesses[0].type, 'get');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('restores original property value', (t) => {
|
||||||
|
const obj = {
|
||||||
|
foo: 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
const prop = t.mock.property(obj, 'foo', 20);
|
||||||
|
assert.strictEqual(obj.foo, 20);
|
||||||
|
|
||||||
|
prop.mock.restore();
|
||||||
|
assert.strictEqual(obj.foo, 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throws if setting a non-writable property', (t) => {
|
||||||
|
const obj = {};
|
||||||
|
Object.defineProperty(obj, 'bar', {
|
||||||
|
value: 1,
|
||||||
|
writable: false,
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
t.mock.property(obj, 'bar', 2);
|
||||||
|
assert.strictEqual(obj.bar, 2);
|
||||||
|
|
||||||
|
assert.throws(() => { obj.bar = 3; }, { code: 'ERR_INVALID_ARG_VALUE' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throws if property does not exist', (t) => {
|
||||||
|
assert.throws(() => {
|
||||||
|
t.mock.property({}, 'doesNotExist', 1);
|
||||||
|
}, { code: 'ERR_INVALID_ARG_VALUE' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throws if object is null', (t) => {
|
||||||
|
assert.throws(() => {
|
||||||
|
t.mock.property(null, 'foo', 1);
|
||||||
|
}, { code: 'ERR_INVALID_ARG_TYPE' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('local property mocks are auto restored after the test finishes', async (t) => {
|
||||||
|
const obj = { foo: 111, bar: 222 };
|
||||||
|
|
||||||
|
assert.strictEqual(obj.foo, 111);
|
||||||
|
assert.strictEqual(obj.bar, 222);
|
||||||
|
|
||||||
|
t.mock.property(obj, 'foo', 888);
|
||||||
|
|
||||||
|
assert.strictEqual(obj.foo, 888);
|
||||||
|
assert.strictEqual(obj.bar, 222);
|
||||||
|
|
||||||
|
t.beforeEach(() => {
|
||||||
|
assert.strictEqual(obj.foo, 888);
|
||||||
|
assert.strictEqual(obj.bar, 222);
|
||||||
|
});
|
||||||
|
|
||||||
|
t.afterEach(() => {
|
||||||
|
assert.strictEqual(obj.foo, 888);
|
||||||
|
assert.strictEqual(obj.bar, 999);
|
||||||
|
});
|
||||||
|
|
||||||
|
await t.test('creates property mocks that are auto restored', (t) => {
|
||||||
|
t.mock.property(obj, 'bar', 999);
|
||||||
|
|
||||||
|
assert.strictEqual(obj.foo, 888);
|
||||||
|
assert.strictEqual(obj.bar, 999);
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(obj.foo, 888);
|
||||||
|
assert.strictEqual(obj.bar, 222);
|
||||||
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user