feat: Allow mocking property value in tests (#13496)

This commit is contained in:
Michal Kočárek 2023-01-04 11:21:38 +01:00 committed by GitHub
parent a325f8790d
commit e77128b128
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 689 additions and 10 deletions

View File

@ -2,6 +2,7 @@
### Features
- `[@jest/globals, jest-mock]` Add `jest.replaceProperty()` that replaces property value ([#13496](https://github.com/facebook/jest/pull/13496))
- `[expect, @jest/expect-utils]` Support custom equality testers ([#13654](https://github.com/facebook/jest/pull/13654))
- `[jest-haste-map]` ignore Sapling vcs directories (`.sl/`) ([#13674](https://github.com/facebook/jest/pull/13674))
- `[jest-resolve]` Support subpath imports ([#13705](https://github.com/facebook/jest/pull/13705))

View File

@ -608,13 +608,62 @@ See [Mock Functions](MockFunctionAPI.md#jestfnimplementation) page for details o
Determines if the given function is a mocked function.
### `jest.replaceProperty(object, propertyKey, value)`
Replace `object[propertyKey]` with a `value`. The property must already exist on the object. The same property might be replaced multiple times. Returns a Jest [replaced property](MockFunctionAPI.md#replaced-properties).
:::note
To mock properties that are defined as getters or setters, use [`jest.spyOn(object, methodName, accessType)`](#jestspyonobject-methodname-accesstype) instead. To mock functions, use [`jest.spyOn(object, methodName)`](#jestspyonobject-methodname) instead.
:::
:::tip
All properties replaced with `jest.replaceProperty` could be restored to the original value by calling [jest.restoreAllMocks](#jestrestoreallmocks) on [afterEach](GlobalAPI.md#aftereachfn-timeout) method.
:::
Example:
```js
const utils = {
isLocalhost() {
return process.env.HOSTNAME === 'localhost';
},
};
module.exports = utils;
```
Example test:
```js
const utils = require('./utils');
afterEach(() => {
// restore replaced property
jest.restoreAllMocks();
});
test('isLocalhost returns true when HOSTNAME is localhost', () => {
jest.replaceProperty(process, 'env', {HOSTNAME: 'localhost'});
expect(utils.isLocalhost()).toBe(true);
});
test('isLocalhost returns false when HOSTNAME is not localhost', () => {
jest.replaceProperty(process, 'env', {HOSTNAME: 'not-localhost'});
expect(utils.isLocalhost()).toBe(false);
});
```
### `jest.spyOn(object, methodName)`
Creates a mock function similar to `jest.fn` but also tracks calls to `object[methodName]`. Returns a Jest [mock function](MockFunctionAPI.md).
:::note
By default, `jest.spyOn` also calls the **spied** method. This is different behavior from most other test libraries. If you want to overwrite the original function, you can use `jest.spyOn(object, methodName).mockImplementation(() => customImplementation)` or `object[methodName] = jest.fn(() => customImplementation);`
By default, `jest.spyOn` also calls the **spied** method. This is different behavior from most other test libraries. If you want to overwrite the original function, you can use `jest.spyOn(object, methodName).mockImplementation(() => customImplementation)` or `jest.replaceProperty(object, methodName, jest.fn(() => customImplementation));`
:::
@ -713,6 +762,10 @@ test('plays audio', () => {
});
```
### `jest.Replaced<Source>`
See [TypeScript Usage](MockFunctionAPI.md#replacedpropertyreplacevaluevalue) chapter of Mock Functions page for documentation.
### `jest.Spied<Source>`
See [TypeScript Usage](MockFunctionAPI.md#jestspiedsource) chapter of Mock Functions page for documentation.
@ -731,7 +784,7 @@ Returns the `jest` object for chaining.
### `jest.restoreAllMocks()`
Restores all mocks back to their original value. Equivalent to calling [`.mockRestore()`](MockFunctionAPI.md#mockfnmockrestore) on every mocked function. Beware that `jest.restoreAllMocks()` only works when the mock was created with `jest.spyOn`; other mocks will require you to manually restore them.
Restores all mocks and replaced properties back to their original value. Equivalent to calling [`.mockRestore()`](MockFunctionAPI.md#mockfnmockrestore) on every mocked function and [`.restore()`](MockFunctionAPI.md#replacedpropertyrestore) on every replaced property. Beware that `jest.restoreAllMocks()` only works for mocks created with [`jest.spyOn()`](#jestspyonobject-methodname) and properties replaced with [`jest.replaceProperty()`](#jestreplacepropertyobject-propertykey-value); other mocks will require you to manually restore them.
## Fake Timers

View File

@ -515,6 +515,20 @@ test('async test', async () => {
});
```
## Replaced Properties
### `replacedProperty.replaceValue(value)`
Changes the value of already replaced property. This is useful when you want to replace property and then adjust the value in specific tests. As an alternative, you can call [`jest.replaceProperty()`](JestObjectAPI.md#jestreplacepropertyobject-propertykey-value) multiple times on same property.
### `replacedProperty.restore()`
Restores object's property to the original value.
Beware that `replacedProperty.restore()` only works when the property value was replaced with [`jest.replaceProperty()`](JestObjectAPI.md#jestreplacepropertyobject-propertykey-value).
The [`restoreMocks`](configuration#restoremocks-boolean) configuration option is available to restore replaced properties automatically before each test.
## TypeScript Usage
<TypeScriptExamplesNote />
@ -594,6 +608,39 @@ test('returns correct data', () => {
Types of classes, functions or objects can be passed as type argument to `jest.Mocked<Source>`. If you prefer to constrain the input type, use: `jest.MockedClass<Source>`, `jest.MockedFunction<Source>` or `jest.MockedObject<Source>`.
### `jest.Replaced<Source>`
The `jest.Replaced<Source>` utility type returns the `Source` type wrapped with type definitions of Jest [replaced property](#replaced-properties).
```ts title="src/utils.ts"
export function isLocalhost(): boolean {
return process.env['HOSTNAME'] === 'localhost';
}
```
```ts title="src/__tests__/utils.test.ts"
import {afterEach, expect, it, jest} from '@jest/globals';
import {isLocalhost} from '../utils';
let replacedEnv: jest.Replaced<typeof process.env> | undefined = undefined;
afterEach(() => {
replacedEnv?.restore();
});
it('isLocalhost should detect localhost environment', () => {
replacedEnv = jest.replaceProperty(process, 'env', {HOSTNAME: 'localhost'});
expect(isLocalhost()).toBe(true);
});
it('isLocalhost should detect non-localhost environment', () => {
replacedEnv = jest.replaceProperty(process, 'env', {HOSTNAME: 'example.com'});
expect(isLocalhost()).toBe(false);
});
```
### `jest.mocked(source, options?)`
The `mocked()` helper method wraps types of the `source` object and its deep nested members with type definitions of Jest mock function. You can pass `{shallow: true}` as the `options` argument to disable the deeply mocked behavior.

View File

@ -0,0 +1,17 @@
import {isLocalhost} from '../utils';
afterEach(() => {
jest.restoreAllMocks();
});
it('isLocalhost should detect localhost environment', () => {
jest.replaceProperty(process, 'env', {HOSTNAME: 'localhost'});
expect(isLocalhost()).toBe(true);
});
it('isLocalhost should detect non-localhost environment', () => {
jest.replaceProperty(process, 'env', {HOSTNAME: 'example.com'});
expect(isLocalhost()).toBe(false);
});

View File

@ -0,0 +1,3 @@
export function isLocalhost() {
return process.env.HOSTNAME === 'localhost';
}

View File

@ -0,0 +1,24 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
import {afterEach, beforeEach, expect, it, jest} from '@jest/globals';
import {isLocalhost} from '../utils';
let replacedEnv: jest.Replaced<typeof process.env> | undefined = undefined;
beforeEach(() => {
replacedEnv = jest.replaceProperty(process, 'env', {});
});
afterEach(() => {
replacedEnv?.restore();
});
it('isLocalhost should detect localhost environment', () => {
replacedEnv.replaceValue({HOSTNAME: 'localhost'});
expect(isLocalhost()).toBe(true);
});
it('isLocalhost should detect non-localhost environment', () => {
expect(isLocalhost()).toBe(false);
});

View File

@ -0,0 +1,5 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
export function isLocalhost() {
return process.env.HOSTNAME === 'localhost';
}

View File

@ -223,6 +223,13 @@ export interface Jest {
* mocked behavior.
*/
mocked: ModuleMocker['mocked'];
/**
* Replaces property on an object with another value.
*
* @remarks
* For mocking functions or 'get' or 'set' accessors, use `jest.spyOn()` instead.
*/
replaceProperty: ModuleMocker['replaceProperty'];
/**
* Returns a mock module instead of the actual module, bypassing all checks
* on whether the module should be required normally or not.
@ -239,8 +246,9 @@ export interface Jest {
*/
resetModules(): Jest;
/**
* Restores all mocks back to their original value. Equivalent to calling
* `.mockRestore()` on every mocked function.
* Restores all mocks and replaced properties back to their original value.
* Equivalent to calling `.mockRestore()` on every mocked function
* and `.restore()` on every replaced property.
*
* Beware that `jest.restoreAllMocks()` only works when the mock was created
* with `jest.spyOn()`; other mocks will require you to manually restore them.

View File

@ -16,6 +16,7 @@ import type {
MockedClass as JestMockedClass,
MockedFunction as JestMockedFunction,
MockedObject as JestMockedObject,
Replaced as JestReplaced,
Spied as JestSpied,
SpiedClass as JestSpiedClass,
SpiedFunction as JestSpiedFunction,
@ -63,6 +64,10 @@ declare namespace jest {
* Wraps an object type with Jest mock type definitions.
*/
export type MockedObject<T extends object> = JestMockedObject<T>;
/**
* Constructs the type of a replaced property.
*/
export type Replaced<T> = JestReplaced<T>;
/**
* Constructs the type of a spied class or function.
*/

View File

@ -15,11 +15,13 @@ import {
} from 'tsd-lite';
import {
Mock,
Replaced,
SpiedClass,
SpiedFunction,
SpiedGetter,
SpiedSetter,
fn,
replaceProperty,
spyOn,
} from 'jest-mock';
@ -492,3 +494,71 @@ expectError(
(key: string, value: number) => {},
),
);
// replaceProperty + Replaced
const obj = {
fn: () => {},
property: 1,
};
expectType<Replaced<number>>(replaceProperty(obj, 'property', 1));
expectType<void>(replaceProperty(obj, 'property', 1).replaceValue(1).restore());
expectError(replaceProperty(obj, 'invalid', 1));
expectError(replaceProperty(obj, 'property', 'not a number'));
expectError(replaceProperty(obj, 'fn', () => {}));
expectError(replaceProperty(obj, 'property', 1).replaceValue('not a number'));
interface ComplexObject {
numberOrUndefined: number | undefined;
optionalString?: string;
multipleTypes: number | string | {foo: number} | null;
}
declare const complexObject: ComplexObject;
interface ObjectWithDynamicProperties {
[key: string]: boolean;
}
declare const objectWithDynamicProperties: ObjectWithDynamicProperties;
// Resulting type should retain the original property type
expectType<Replaced<number | undefined>>(
replaceProperty(complexObject, 'numberOrUndefined', undefined),
);
expectType<Replaced<number | undefined>>(
replaceProperty(complexObject, 'numberOrUndefined', 1),
);
expectError(
replaceProperty(
complexObject,
'numberOrUndefined',
'string is not valid TypeScript type',
),
);
expectType<Replaced<string | undefined>>(
replaceProperty(complexObject, 'optionalString', 'foo'),
);
expectType<Replaced<string | undefined>>(
replaceProperty(complexObject, 'optionalString', undefined),
);
expectType<Replaced<boolean>>(
replaceProperty(objectWithDynamicProperties, 'dynamic prop 1', true),
);
expectError(
replaceProperty(objectWithDynamicProperties, 'dynamic prop 1', undefined),
);
expectError(replaceProperty(complexObject, 'not a property', undefined));
expectType<Replaced<ComplexObject['multipleTypes']>>(
replaceProperty(complexObject, 'multipleTypes', 1)
.replaceValue('foo')
.replaceValue({foo: 1})
.replaceValue(null),
);

View File

@ -1279,12 +1279,12 @@ describe('moduleMocker', () => {
expect(() => {
moduleMocker.spyOn({}, 'method');
}).toThrow(
'Cannot spy the method property because it is not a function; undefined given instead',
"Cannot spy the method property because it is not a function; undefined given instead. If you are trying to mock a property, use `jest.replaceProperty(object, 'method', value)` instead.",
);
expect(() => {
moduleMocker.spyOn({method: 10}, 'method');
}).toThrow(
'Cannot spy the method property because it is not a function; number given instead',
"Cannot spy the method property because it is not a function; number given instead. If you are trying to mock a property, use `jest.replaceProperty(object, 'method', value)` instead.",
);
});
@ -1449,12 +1449,12 @@ describe('moduleMocker', () => {
expect(() => {
moduleMocker.spyOn({}, 'method');
}).toThrow(
'Cannot spy the method property because it is not a function; undefined given instead',
"Cannot spy the method property because it is not a function; undefined given instead. If you are trying to mock a property, use `jest.replaceProperty(object, 'method', value)` instead.",
);
expect(() => {
moduleMocker.spyOn({method: 10}, 'method');
}).toThrow(
'Cannot spy the method property because it is not a function; number given instead',
"Cannot spy the method property because it is not a function; number given instead. If you are trying to mock a property, use `jest.replaceProperty(object, 'method', value)` instead.",
);
});
@ -1604,6 +1604,236 @@ describe('moduleMocker', () => {
expect(spy2.mock.calls).toHaveLength(1);
});
});
describe('replaceProperty', () => {
it('should work', () => {
const obj = {
property: 1,
};
const replaced = moduleMocker.replaceProperty(obj, 'property', 2);
expect(obj.property).toBe(2);
replaced.restore();
expect(obj.property).toBe(1);
});
it('should allow mocking a property multiple times', () => {
const obj = {
property: 1,
};
const replacedFirst = moduleMocker.replaceProperty(obj, 'property', 2);
const replacedSecond = moduleMocker.replaceProperty(obj, 'property', 3);
expect(obj.property).toBe(3);
replacedSecond.restore();
expect(obj.property).toBe(1);
replacedFirst.restore();
expect(obj.property).toBe(1);
});
it('should allow mocking with value of different value', () => {
const obj = {
property: 1,
};
const replaced = moduleMocker.replaceProperty(obj, 'property', {
foo: 'bar',
});
expect(obj.property).toStrictEqual({foo: 'bar'});
replaced.restore();
expect(obj.property).toBe(1);
});
describe('should throw', () => {
it.each`
value
${null}
${undefined}
`('when $value is provided instead of an object', ({value}) => {
expect(() => {
moduleMocker.replaceProperty(value, 'property', 1);
}).toThrow(
'replaceProperty could not find an object on which to replace property',
);
});
it.each`
value | type
${'foo'} | ${'string'}
${1} | ${'number'}
${NaN} | ${'number'}
${1n} | ${'bigint'}
${Symbol()} | ${'symbol'}
${true} | ${'boolean'}
${false} | ${'boolean'}
${() => {}} | ${'function'}
`(
'when primitive value $value is provided instead of an object',
({value, type}) => {
expect(() => {
moduleMocker.replaceProperty(value, 'property', 1);
}).toThrow(
`Cannot mock property on a non-object value; ${type} given`,
);
},
);
it('when property name is not provided', () => {
expect(() => {
moduleMocker.replaceProperty({}, null, 1);
}).toThrow('No property name supplied');
});
it('when property is not defined', () => {
expect(() => {
moduleMocker.replaceProperty({}, 'doesNotExist', 1);
}).toThrow('doesNotExist property does not exist');
});
it('when property is not configurable', () => {
expect(() => {
const obj = {};
Object.defineProperty(obj, 'property', {
configurable: false,
value: 1,
writable: false,
});
moduleMocker.replaceProperty(obj, 'property', 2);
}).toThrow('property is not declared configurable');
});
it('when trying to mock a method', () => {
expect(() => {
moduleMocker.replaceProperty({method: () => {}}, 'method', () => {});
}).toThrow(
"Cannot mock the method property because it is a function. Use `jest.spyOn(object, 'method')` instead.",
);
});
it('when mocking a getter', () => {
const obj = {
get getter() {
return 1;
},
};
expect(() => {
moduleMocker.replaceProperty(obj, 'getter', 1);
}).toThrow('Cannot mock the getter property because it has a getter');
});
it('when mocking a setter', () => {
const obj = {
// eslint-disable-next-line accessor-pairs
set setter(_value: number) {},
};
expect(() => {
moduleMocker.replaceProperty(obj, 'setter', 1);
}).toThrow('Cannot mock the setter property because it has a setter');
});
});
it('should work for property from prototype chain', () => {
const parent = {property: 'abcd'};
const child = Object.create(parent);
const replaced = moduleMocker.replaceProperty(child, 'property', 'defg');
expect(child.property).toBe('defg');
replaced.restore();
expect(child.property).toBe('abcd');
expect(
Object.getOwnPropertyDescriptor(child, 'property'),
).toBeUndefined();
});
describe('with restoreAllMocks', () => {
it('should work', () => {
const obj = {
property: 1,
};
const replaced = moduleMocker.replaceProperty(obj, 'property', 2);
expect(obj.property).toBe(2);
moduleMocker.restoreAllMocks();
expect(obj.property).toBe(1);
// Just make sure that this call won't break anything while calling after the property has been already restored
replaced.restore();
expect(obj.property).toBe(1);
});
it('should work for property mocked multiple times', () => {
const obj = {
property: 1,
};
const replaced1 = moduleMocker.replaceProperty(obj, 'property', 2);
const replaced2 = moduleMocker.replaceProperty(obj, 'property', 3);
expect(obj.property).toBe(3);
moduleMocker.restoreAllMocks();
expect(obj.property).toBe(1);
// Just make sure that this call won't break anything while calling after the property has been already restored
replaced2.restore();
replaced1.restore();
expect(obj.property).toBe(1);
});
});
describe('replaceValue', () => {
it('should work', () => {
const obj = {
property: 1,
};
const replaced = moduleMocker.replaceProperty(obj, 'property', 2);
const result = replaced.replaceValue(3);
expect(obj.property).toBe(3);
expect(result).toBe(replaced);
});
it('should work while passing different type', () => {
const obj = {
property: 1,
};
const replaced = moduleMocker.replaceProperty(obj, 'property', 2);
const result = replaced.replaceValue('foo');
expect(obj.property).toBe('foo');
expect(result).toBe(replaced);
});
});
});
});
describe('mocked', () => {

View File

@ -172,6 +172,28 @@ export interface MockInstance<T extends FunctionLike = UnknownFunction> {
mockRejectedValueOnce(value: RejectType<T>): this;
}
export interface Replaced<T = unknown> {
/**
* Restore property to its original value known at the time of mocking.
*/
restore(): void;
/**
* Change the value of the property.
*/
replaceValue(value: T): this;
}
type ReplacedPropertyRestorer<
T extends object,
K extends PropertyLikeKeys<T>,
> = {
(): void;
object: T;
property: K;
replaced: Replaced<T[K]>;
};
type MockFunctionResultIncomplete = {
type: 'incomplete';
/**
@ -956,6 +978,27 @@ export class ModuleMocker {
return mock as Mocked<T>;
}
/**
* Check whether the given property of an object has been already replaced.
*/
private _findReplacedProperty<
T extends object,
K extends PropertyLikeKeys<T>,
>(object: T, propertyKey: K): ReplacedPropertyRestorer<T, K> | undefined {
for (const spyState of this._spyState) {
if (
'object' in spyState &&
'property' in spyState &&
spyState.object === object &&
spyState.property === propertyKey
) {
return spyState as ReplacedPropertyRestorer<T, K>;
}
}
return;
}
/**
* @see README.md
* @param metadata Metadata for the mock in the schema returned by the
@ -1123,7 +1166,13 @@ export class ModuleMocker {
methodKey,
)} property because it is not a function; ${this._typeOf(
original,
)} given instead`,
)} given instead.${
typeof original !== 'object'
? ` If you are trying to mock a property, use \`jest.replaceProperty(object, '${String(
methodKey,
)}', value)\` instead.`
: ''
}`,
);
}
@ -1208,7 +1257,13 @@ export class ModuleMocker {
propertyKey,
)} property because it is not a function; ${this._typeOf(
original,
)} given instead`,
)} given instead.${
typeof original !== 'object'
? ` If you are trying to mock a property, use \`jest.replaceProperty(object, '${String(
propertyKey,
)}', value)\` instead.`
: ''
}`,
);
}
@ -1230,6 +1285,117 @@ export class ModuleMocker {
return descriptor[accessType] as Mock;
}
replaceProperty<
T extends object,
K extends PropertyLikeKeys<T>,
V extends T[K],
>(object: T, propertyKey: K, value: V): Replaced<T[K]> {
if (object === undefined || object == null) {
throw new Error(
`replaceProperty could not find an object on which to replace ${String(
propertyKey,
)}`,
);
}
if (propertyKey === undefined || propertyKey === null) {
throw new Error('No property name supplied');
}
if (typeof object !== 'object') {
throw new Error(
`Cannot mock property on a non-object value; ${this._typeOf(
object,
)} given`,
);
}
let descriptor = Object.getOwnPropertyDescriptor(object, propertyKey);
let proto = Object.getPrototypeOf(object);
while (!descriptor && proto !== null) {
descriptor = Object.getOwnPropertyDescriptor(proto, propertyKey);
proto = Object.getPrototypeOf(proto);
}
if (!descriptor) {
throw new Error(`${String(propertyKey)} property does not exist`);
}
if (!descriptor.configurable) {
throw new Error(`${String(propertyKey)} is not declared configurable`);
}
if (descriptor.get !== undefined) {
throw new Error(
`Cannot mock the ${String(
propertyKey,
)} property because it has a getter. Use \`jest.spyOn(object, '${String(
propertyKey,
)}', 'get').mockReturnValue(value)\` instead.`,
);
}
if (descriptor.set !== undefined) {
throw new Error(
`Cannot mock the ${String(
propertyKey,
)} property because it has a setter. Use \`jest.spyOn(object, '${String(
propertyKey,
)}', 'set').mockReturnValue(value)\` instead.`,
);
}
if (typeof descriptor.value === 'function') {
throw new Error(
`Cannot mock the ${String(
propertyKey,
)} property because it is a function. Use \`jest.spyOn(object, '${String(
propertyKey,
)}')\` instead.`,
);
}
const existingRestore = this._findReplacedProperty(object, propertyKey);
if (existingRestore) {
return existingRestore.replaced.replaceValue(value);
}
const isPropertyOwner = Object.prototype.hasOwnProperty.call(
object,
propertyKey,
);
const originalValue = descriptor.value;
const restore: ReplacedPropertyRestorer<T, K> = () => {
if (isPropertyOwner) {
object[propertyKey] = originalValue;
} else {
delete object[propertyKey];
}
};
const replaced: Replaced<T[K]> = {
replaceValue: value => {
object[propertyKey] = value;
return replaced;
},
restore: () => {
restore();
this._spyState.delete(restore);
},
};
restore.object = object;
restore.property = propertyKey;
restore.replaced = replaced;
this._spyState.add(restore);
return replaced.replaceValue(value);
}
clearAllMocks(): void {
this._mockState = new WeakMap();
}
@ -1266,3 +1432,4 @@ const JestMock = new ModuleMocker(globalThis);
export const fn = JestMock.fn.bind(JestMock);
export const spyOn = JestMock.spyOn.bind(JestMock);
export const mocked = JestMock.mocked.bind(JestMock);
export const replaceProperty = JestMock.replaceProperty.bind(JestMock);

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
'use strict';
let createRuntime;
let obj;
describe('Runtime', () => {
beforeEach(() => {
createRuntime = require('createRuntime');
obj = {
property: 1,
};
});
describe('jest.replaceProperty', () => {
it('should work', async () => {
const runtime = await createRuntime(__filename);
const root = runtime.requireModule(runtime.__mockRootPath);
const mocked = root.jest.replaceProperty(obj, 'property', 2);
expect(obj.property).toBe(2);
mocked.replaceValue(3);
expect(obj.property).toBe(3);
mocked.restore();
expect(obj.property).toBe(1);
});
});
});

View File

@ -2193,6 +2193,9 @@ export default class Runtime {
'Your test environment does not support `mocked`, please update it.',
);
});
const replaceProperty = this._moduleMocker.replaceProperty.bind(
this._moduleMocker,
);
const setTimeout = (timeout: number) => {
this._environment.global[testTimeoutSymbol] = timeout;
@ -2253,6 +2256,7 @@ export default class Runtime {
mock,
mocked,
now: () => _getFakeTimers().now(),
replaceProperty,
requireActual: moduleName => this.requireActual(from, moduleName),
requireMock: moduleName => this.requireMock(from, moduleName),
resetAllMocks,

View File

@ -266,6 +266,8 @@ expectType<ModuleMocker['fn']>(jest.fn);
expectType<ModuleMocker['spyOn']>(jest.spyOn);
expectType<ModuleMocker['replaceProperty']>(jest.replaceProperty);
// Mock<T>
expectType<Mock<() => boolean>>({} as jest.Mock<() => boolean>);
@ -447,6 +449,12 @@ expectError(
expectAssignable<typeof someObject>(mockObjectB);
// Replaced
expectAssignable<jest.Replaced<number>>(
jest.replaceProperty(someObject, 'propertyA', 123),
);
// Spied
expectAssignable<jest.Spied<typeof someObject.methodA>>(