diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4a8c3863378b..dca7f2bb486d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,8 +23,9 @@
- `[@jest/expect-utils]` New module exporting utils for `expect` ([#12323](https://github.com/facebook/jest/pull/12323))
- `[jest-haste-map]` [**BREAKING**] `HasteMap.create` now returns a promise ([#12008](https://github.com/facebook/jest/pull/12008))
- `[jest-haste-map]` Add support for `dependencyExtractor` written in ESM ([#12008](https://github.com/facebook/jest/pull/12008))
-- `[jest-mock]` [**BREAKING**] Rename exported utility types `ConstructorLike`, `MethodLike`, `ConstructorLikeKeys`, `MethodLikeKeys`, `PropertyLikeKeys`; remove exports of utility types `ArgumentsOf`, `ArgsType`, `ConstructorArgumentsOf` - TS builtin utility types `ConstructorParameters` and `Parameters` should be used instead ([#12435](https://github.com/facebook/jest/pull/12435))
+- `[jest-mock]` [**BREAKING**] Rename exported utility types `ClassLike`, `FunctionLike`, `ConstructorLikeKeys`, `MethodLikeKeys`, `PropertyLikeKeys`; remove exports of utility types `ArgumentsOf`, `ArgsType`, `ConstructorArgumentsOf` - TS builtin utility types `ConstructorParameters` and `Parameters` should be used instead ([#12435](https://github.com/facebook/jest/pull/12435), [#12489](https://github.com/facebook/jest/pull/12489))
- `[jest-mock]` Improve `isMockFunction` to infer types of passed function ([#12442](https://github.com/facebook/jest/pull/12442))
+- `[jest-mock]` [**BREAKING**] Improve the usage of `jest.fn` generic type argument ([#12489](https://github.com/facebook/jest/pull/12489))
- `[jest-mock]` Add support for auto-mocking async generator functions ([#11080](https://github.com/facebook/jest/pull/11080))
- `[jest-resolve]` [**BREAKING**] Add support for `package.json` `exports` ([#11961](https://github.com/facebook/jest/pull/11961), [#12373](https://github.com/facebook/jest/pull/12373))
- `[jest-resolve, jest-runtime]` Add support for `data:` URI import and mock ([#12392](https://github.com/facebook/jest/pull/12392))
diff --git a/docs/JestObjectAPI.md b/docs/JestObjectAPI.md
index 16ca6cdcd6f5..0449d43a24bd 100644
--- a/docs/JestObjectAPI.md
+++ b/docs/JestObjectAPI.md
@@ -453,7 +453,7 @@ const otherCopyOfMyModule = require('myModule');
## Mock Functions
-### `jest.fn(implementation)`
+### `jest.fn(implementation?)`
Returns a new, unused [mock function](MockFunctionAPI.md). Optionally takes a mock implementation.
@@ -467,6 +467,12 @@ const returnsTrue = jest.fn(() => true);
console.log(returnsTrue()); // true;
```
+:::note
+
+See [Mock Functions](MockFunctionAPI.md#jestfnimplementation) page for details on TypeScript usage.
+
+:::
+
### `jest.isMockFunction(fn)`
Determines if the given function is a mocked function.
diff --git a/docs/MockFunctionAPI.md b/docs/MockFunctionAPI.md
index 388a3f0fa836..1c3a50555100 100644
--- a/docs/MockFunctionAPI.md
+++ b/docs/MockFunctionAPI.md
@@ -5,6 +5,16 @@ title: Mock Functions
Mock functions are also known as "spies", because they let you spy on the behavior of a function that is called indirectly by some other code, rather than only testing the output. You can create a mock function with `jest.fn()`. If no implementation is given, the mock function will return `undefined` when invoked.
+:::info
+
+The TypeScript examples from this page will only work as document if you import `jest` from `'@jest/globals'`:
+
+```ts
+import {jest} from '@jest/globals';
+```
+
+:::
+
## Methods
import TOCInline from "@theme/TOCInline"
@@ -15,6 +25,8 @@ import TOCInline from "@theme/TOCInline"
## Reference
+import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';
+
### `mockFn.getMockName()`
Returns the mock name string set by calling `mockFn.mockName(value)`.
@@ -93,7 +105,7 @@ For example: A mock function `f` that has been called twice, with the arguments
Clears all information stored in the [`mockFn.mock.calls`](#mockfnmockcalls), [`mockFn.mock.instances`](#mockfnmockinstances) and [`mockFn.mock.results`](#mockfnmockresults) arrays. Often this is useful when you want to clean up a mocks usage data between two assertions.
-Beware that `mockClear` will replace `mockFn.mock`, not just these three properties! You should, therefore, avoid assigning `mockFn.mock` to other variables, temporary or not, to make sure you don't access stale data.
+Beware that `mockFn.mockClear()` will replace `mockFn.mock`, not just these three properties! You should, therefore, avoid assigning `mockFn.mock` to other variables, temporary or not, to make sure you don't access stale data.
The [`clearMocks`](configuration#clearmocks-boolean) configuration option is available to clear mocks automatically before each tests.
@@ -111,7 +123,7 @@ Does everything that [`mockFn.mockReset()`](#mockfnmockreset) does, and also res
This is useful when you want to mock functions in certain test cases and restore the original implementation in others.
-Beware that `mockFn.mockRestore` only works when the mock was created with `jest.spyOn`. Thus you have to take care of restoration yourself when manually assigning `jest.fn()`.
+Beware that `mockFn.mockRestore()` only works when the mock was created with `jest.spyOn()`. Thus you have to take care of restoration yourself when manually assigning `jest.fn()`.
The [`restoreMocks`](configuration#restoremocks-boolean) configuration option is available to restore mocks automatically before each test.
@@ -119,82 +131,141 @@ The [`restoreMocks`](configuration#restoremocks-boolean) configuration option is
Accepts a function that should be used as the implementation of the mock. The mock itself will still record all calls that go into and instances that come from itself – the only difference is that the implementation will also be executed when the mock is called.
-_Note: `jest.fn(implementation)` is a shorthand for `jest.fn().mockImplementation(implementation)`._
+:::tip
-For example:
+`jest.fn(implementation)` is a shorthand for `jest.fn().mockImplementation(implementation)`.
+
+:::
```js
-const mockFn = jest.fn().mockImplementation(scalar => 42 + scalar);
-// or: jest.fn(scalar => 42 + scalar);
+const mockFn = jest.fn(scalar => 42 + scalar);
-const a = mockFn(0);
-const b = mockFn(1);
+mockFn(0); // 42
+mockFn(1); // 43
-a === 42; // true
-b === 43; // true
+mockFn.mockImplementation(scalar => 36 + scalar);
-mockFn.mock.calls[0][0] === 0; // true
-mockFn.mock.calls[1][0] === 1; // true
+mockFn(2); // 38
+mockFn(3); // 39
```
`mockImplementation` can also be used to mock class constructors:
+
+
+
```js title="SomeClass.js"
module.exports = class SomeClass {
- m(a, b) {}
+ method(a, b) {}
};
```
-```js title="OtherModule.test.js"
-jest.mock('./SomeClass'); // this happens automatically with automocking
+```js title="SomeClass.test.js"
const SomeClass = require('./SomeClass');
-const mMock = jest.fn();
+
+jest.mock('./SomeClass'); // this happens automatically with automocking
+
+const mockMethod = jest.fn();
+SomeClass.mockImplementation(() => {
+ return {
+ method: mockMethod,
+ };
+});
+
+const some = new SomeClass();
+some.method('a', 'b');
+
+console.log('Calls to method: ', mockMethod.mock.calls);
+```
+
+
+
+
+
+```ts title="SomeClass.ts"
+export class SomeClass {
+ method(a: string, b: string): void {}
+}
+```
+
+```ts title="SomeClass.test.ts"
+import {SomeClass} from './SomeClass';
+
+jest.mock('./SomeClass'); // this happens automatically with automocking
+
+const mockMethod = jest.fn<(a: string, b: string) => void>();
SomeClass.mockImplementation(() => {
return {
- m: mMock,
+ method: mockMethod,
};
});
const some = new SomeClass();
-some.m('a', 'b');
-console.log('Calls to m: ', mMock.mock.calls);
+some.method('a', 'b');
+
+console.log('Calls to method: ', mockMethod.mock.calls);
```
+
+
+
### `mockFn.mockImplementationOnce(fn)`
Accepts a function that will be used as an implementation of the mock for one call to the mocked function. Can be chained so that multiple function calls produce different results.
+
+
+
```js
-const myMockFn = jest
+const mockFn = jest
.fn()
.mockImplementationOnce(cb => cb(null, true))
.mockImplementationOnce(cb => cb(null, false));
-myMockFn((err, val) => console.log(val)); // true
+mockFn((err, val) => console.log(val)); // true
+mockFn((err, val) => console.log(val)); // false
+```
+
+
+
+
-myMockFn((err, val) => console.log(val)); // false
+```ts
+const mockFn = jest
+ .fn<(cb: (a: null, b: boolean) => void) => void>()
+ .mockImplementationOnce(cb => cb(null, true))
+ .mockImplementationOnce(cb => cb(null, false));
+
+mockFn((err, val) => console.log(val)); // true
+mockFn((err, val) => console.log(val)); // false
```
-When the mocked function runs out of implementations defined with mockImplementationOnce, it will execute the default implementation set with `jest.fn(() => defaultValue)` or `.mockImplementation(() => defaultValue)` if they were called:
+
+
+
+When the mocked function runs out of implementations defined with `.mockImplementationOnce()`, it will execute the default implementation set with `jest.fn(() => defaultValue)` or `.mockImplementation(() => defaultValue)` if they were called:
```js
-const myMockFn = jest
+const mockFn = jest
.fn(() => 'default')
.mockImplementationOnce(() => 'first call')
.mockImplementationOnce(() => 'second call');
-// 'first call', 'second call', 'default', 'default'
-console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());
+mockFn(); // 'first call'
+mockFn(); // 'second call'
+mockFn(); // 'default'
+mockFn(); // 'default'
```
-### `mockFn.mockName(value)`
+### `mockFn.mockName(name)`
-Accepts a string to use in test result output in place of "jest.fn()" to indicate which mock function is being referenced.
+Accepts a string to use in test result output in place of `'jest.fn()'` to indicate which mock function is being referenced.
For example:
```js
const mockFn = jest.fn().mockName('mockedFunction');
+
// mockFn();
expect(mockFn).toHaveBeenCalled();
```
@@ -221,29 +292,76 @@ jest.fn(function () {
Accepts a value that will be returned whenever the mock function is called.
+
+
+
```js
const mock = jest.fn();
+
+mock.mockReturnValue(42);
+mock(); // 42
+
+mock.mockReturnValue(43);
+mock(); // 43
+```
+
+
+
+
+
+```ts
+const mock = jest.fn<() => number>();
+
mock.mockReturnValue(42);
mock(); // 42
+
mock.mockReturnValue(43);
mock(); // 43
```
+
+
+
### `mockFn.mockReturnValueOnce(value)`
Accepts a value that will be returned for one call to the mock function. Can be chained so that successive calls to the mock function return different values. When there are no more `mockReturnValueOnce` values to use, calls will return a value specified by `mockReturnValue`.
+
+
+
```js
-const myMockFn = jest
+const mockFn = jest
.fn()
.mockReturnValue('default')
.mockReturnValueOnce('first call')
.mockReturnValueOnce('second call');
-// 'first call', 'second call', 'default', 'default'
-console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());
+mockFn(); // 'first call'
+mockFn(); // 'second call'
+mockFn(); // 'default'
+mockFn(); // 'default'
+```
+
+
+
+
+
+```ts
+const mockFn = jest
+ .fn<() => string>()
+ .mockReturnValue('default')
+ .mockReturnValueOnce('first call')
+ .mockReturnValueOnce('second call');
+
+mockFn(); // 'first call'
+mockFn(); // 'second call'
+mockFn(); // 'default'
+mockFn(); // 'default'
```
+
+
+
### `mockFn.mockResolvedValue(value)`
Syntactic sugar function for:
@@ -254,6 +372,9 @@ jest.fn().mockImplementation(() => Promise.resolve(value));
Useful to mock async functions in async tests:
+
+
+
```js
test('async test', async () => {
const asyncMock = jest.fn().mockResolvedValue(43);
@@ -262,6 +383,21 @@ test('async test', async () => {
});
```
+
+
+
+
+```ts
+test('async test', async () => {
+ const asyncMock = jest.fn<() => Promise>().mockResolvedValue(43);
+
+ await asyncMock(); // 43
+});
+```
+
+
+
+
### `mockFn.mockResolvedValueOnce(value)`
Syntactic sugar function for:
@@ -272,6 +408,9 @@ jest.fn().mockImplementationOnce(() => Promise.resolve(value));
Useful to resolve different values over multiple async calls:
+
+
+
```js
test('async test', async () => {
const asyncMock = jest
@@ -280,13 +419,35 @@ test('async test', async () => {
.mockResolvedValueOnce('first call')
.mockResolvedValueOnce('second call');
- await asyncMock(); // first call
- await asyncMock(); // second call
- await asyncMock(); // default
- await asyncMock(); // default
+ await asyncMock(); // 'first call'
+ await asyncMock(); // 'second call'
+ await asyncMock(); // 'default'
+ await asyncMock(); // 'default'
+});
+```
+
+
+
+
+
+```ts
+test('async test', async () => {
+ const asyncMock = jest
+ .fn<() => Promise>()
+ .mockResolvedValue('default')
+ .mockResolvedValueOnce('first call')
+ .mockResolvedValueOnce('second call');
+
+ await asyncMock(); // 'first call'
+ await asyncMock(); // 'second call'
+ await asyncMock(); // 'default'
+ await asyncMock(); // 'default'
});
```
+
+
+
### `mockFn.mockRejectedValue(value)`
Syntactic sugar function for:
@@ -297,14 +458,36 @@ jest.fn().mockImplementation(() => Promise.reject(value));
Useful to create async mock functions that will always reject:
+
+
+
```js
test('async test', async () => {
- const asyncMock = jest.fn().mockRejectedValue(new Error('Async error'));
+ const asyncMock = jest
+ .fn()
+ .mockRejectedValue(new Error('Async error message'));
+
+ await asyncMock(); // throws 'Async error message'
+});
+```
+
+
+
+
+
+```ts
+test('async test', async () => {
+ const asyncMock = jest
+ .fn<() => Promise>()
+ .mockRejectedValue(new Error('Async error message'));
- await asyncMock(); // throws "Async error"
+ await asyncMock(); // throws 'Async error message'
});
```
+
+
+
### `mockFn.mockRejectedValueOnce(value)`
Syntactic sugar function for:
@@ -313,78 +496,64 @@ Syntactic sugar function for:
jest.fn().mockImplementationOnce(() => Promise.reject(value));
```
-Example usage:
+Useful together with `.mockResolvedValueOnce()` or to reject with different exceptions over multiple async calls:
+
+
+
```js
test('async test', async () => {
const asyncMock = jest
.fn()
.mockResolvedValueOnce('first call')
- .mockRejectedValueOnce(new Error('Async error'));
+ .mockRejectedValueOnce(new Error('Async error message'));
- await asyncMock(); // first call
- await asyncMock(); // throws "Async error"
+ await asyncMock(); // 'first call'
+ await asyncMock(); // throws 'Async error message'
});
```
-## TypeScript
-
-Jest itself is written in [TypeScript](https://www.typescriptlang.org).
+
-If you are using [Create React App](https://create-react-app.dev) then the [TypeScript template](https://create-react-app.dev/docs/adding-typescript/) has everything you need to start writing tests in TypeScript.
+
-Otherwise, please see our [Getting Started](GettingStarted.md#using-typescript) guide for to get setup with TypeScript.
-
-You can see an example of using Jest with TypeScript in our [GitHub repository](https://github.com/facebook/jest/tree/main/examples/typescript).
-
-### `jest.MockedFunction`
-
-> `jest.MockedFunction` is available in the `@types/jest` module from version `24.9.0`.
-
-The following examples will assume you have an understanding of how [Jest mock functions work with JavaScript](MockFunctions.md).
+```ts
+test('async test', async () => {
+ const asyncMock = jest
+ .fn<() => Promise>()
+ .mockResolvedValueOnce('first call')
+ .mockRejectedValueOnce(new Error('Async error message'));
-You can use `jest.MockedFunction` to represent a function that has been replaced by a Jest mock.
+ await asyncMock(); // 'first call'
+ await asyncMock(); // throws 'Async error message'
+});
+```
-Example using [automatic `jest.mock`](JestObjectAPI.md#jestmockmodulename-factory-options):
+
+
-```ts
-// Assume `add` is imported and used within `calculate`.
-import add from './add';
-import calculate from './calc';
+## TypeScript Usage
-jest.mock('./add');
+:::tip
-// Our mock of `add` is now fully typed
-const mockAdd = add as jest.MockedFunction;
+Please consult the [Getting Started](GettingStarted.md#using-typescript) guide for details on how to setup Jest with TypeScript.
-test('calculate calls add', () => {
- calculate('Add', 1, 2);
+:::
- expect(mockAdd).toBeCalledTimes(1);
- expect(mockAdd).toBeCalledWith(1, 2);
-});
-```
+### `jest.fn(implementation?)`
-Example using [`jest.fn`](JestObjectAPI.md#jestfnimplementation):
+Correct mock typings will be inferred, if implementation is passed to [`jest.fn()`](JestObjectAPI.md#jestfnimplementation). There are many use cases there the implementation is omitted. To ensure type safety you may pass a generic type argument (also see the examples above for more reference):
```ts
-// Here `add` is imported for its type
-import add from './add';
+import {expect, jest, test} from '@jest/globals';
+import type add from './add';
import calculate from './calc';
test('calculate calls add', () => {
// Create a new mock that can be used in place of `add`.
- const mockAdd = jest.fn() as jest.MockedFunction;
-
- // Note: You can use the `jest.fn` type directly like this if you want:
- // const mockAdd = jest.fn, Parameters>();
- // `jest.MockedFunction` is a more friendly shortcut.
+ const mockAdd = jest.fn();
- // Now we can easily set up mock implementations.
- // All the `.mock*` API can now give you proper types for `add`.
- // https://jestjs.io/docs/mock-function-api
-
- // `.mockImplementation` can now infer that `a` and `b` are `number`
+ // `.mockImplementation()` now can infer that `a` and `b` are `number`
// and that the returned value is a `number`.
mockAdd.mockImplementation((a, b) => {
// Yes, this mock is still adding two numbers but imagine this
@@ -392,67 +561,11 @@ test('calculate calls add', () => {
return a + b;
});
- // `mockAdd` is properly typed and therefore accepted by
- // anything requiring `add`.
+ // `mockAdd` is properly typed and therefore accepted by anything
+ // requiring `add`.
calculate(mockAdd, 1, 2);
expect(mockAdd).toBeCalledTimes(1);
expect(mockAdd).toBeCalledWith(1, 2);
});
```
-
-### `jest.MockedClass`
-
-> `jest.MockedClass` is available in the `@types/jest` module from version `24.9.0`.
-
-The following examples will assume you have an understanding of how [Jest mock classes work with JavaScript](Es6ClassMocks.md).
-
-You can use `jest.MockedClass` to represent a class that has been replaced by a Jest mock.
-
-Converting the [ES6 Class automatic mock example](Es6ClassMocks.md#automatic-mock) would look like this:
-
-```ts
-import SoundPlayer from '../sound-player';
-import SoundPlayerConsumer from '../sound-player-consumer';
-
-jest.mock('../sound-player'); // SoundPlayer is now a mock constructor
-
-const SoundPlayerMock = SoundPlayer as jest.MockedClass;
-
-beforeEach(() => {
- // Clear all instances and calls to constructor and all methods:
- SoundPlayerMock.mockClear();
-});
-
-it('We can check if the consumer called the class constructor', () => {
- const soundPlayerConsumer = new SoundPlayerConsumer();
- expect(SoundPlayerMock).toHaveBeenCalledTimes(1);
-});
-
-it('We can check if the consumer called a method on the class instance', () => {
- // Show that mockClear() is working:
- expect(SoundPlayerMock).not.toHaveBeenCalled();
-
- const soundPlayerConsumer = new SoundPlayerConsumer();
- // Constructor should have been called again:
- expect(SoundPlayerMock).toHaveBeenCalledTimes(1);
-
- const coolSoundFileName = 'song.mp3';
- soundPlayerConsumer.playSomethingCool();
-
- // mock.instances is available with automatic mocks:
- const mockSoundPlayerInstance = SoundPlayerMock.mock.instances[0];
-
- // However, it will not allow access to `.mock` in TypeScript as it
- // is returning `SoundPlayer`. Instead, you can check the calls to a
- // method like this fully typed:
- expect(SoundPlayerMock.prototype.playSoundFile.mock.calls[0][0]).toEqual(
- coolSoundFileName,
- );
- // Equivalent to above check:
- expect(SoundPlayerMock.prototype.playSoundFile).toHaveBeenCalledWith(
- coolSoundFileName,
- );
- expect(SoundPlayerMock.prototype.playSoundFile).toHaveBeenCalledTimes(1);
-});
-```
diff --git a/packages/jest-mock/README.md b/packages/jest-mock/README.md
index da440ff63994..5bc75093f8fa 100644
--- a/packages/jest-mock/README.md
+++ b/packages/jest-mock/README.md
@@ -1,5 +1,7 @@
# jest-mock
+**Note:** More details on user side API can be found in [Jest documentation](https://jestjs.io/docs/mock-function-api).
+
## API
```js
@@ -12,7 +14,7 @@ Creates a new module mocker that generates mocks as if they were created in an e
### `generateFromMetadata(metadata)`
-Generates a mock based on the given metadata (Metadata for the mock in the schema returned by the getMetadata method of this module). Mocks treat functions specially, and all mock functions have additional members, described in the documentation for `fn` in this module.
+Generates a mock based on the given metadata (Metadata for the mock in the schema returned by the `getMetadata()` method of this module). Mocks treat functions specially, and all mock functions have additional members, described in the documentation for `fn()` in this module.
One important note: function prototypes are handled specially by this mocking framework. For functions with prototypes, when called as a constructor, the mock will install mocked function members on the instance. This allows different instances of the same constructor to have different values for its mocks member and its return values.
@@ -56,9 +58,9 @@ const refID = {
};
```
-defines an object with a slot named `self` that refers back to the object.
+Defines an object with a slot named `self` that refers back to the object.
-### `fn`
+### `fn(implementation?)`
Generates a stand-alone function with members that help drive unit tests or confirm expectations. Specifically, functions returned by this method have the following members:
@@ -84,9 +86,15 @@ Sets the default mock implementation for the function.
##### `.mockReturnThis()`
-Syntactic sugar for .mockImplementation(function() {return this;})
+Syntactic sugar for:
+
+```js
+mockFn.mockImplementation(function () {
+ return this;
+});
+```
-In case both `mockImplementationOnce()/mockImplementation()` and `mockReturnValueOnce()/mockReturnValue()` are called. The priority of which to use is based on what is the last call:
+In case both `.mockImplementationOnce()` / `.mockImplementation()` and `.mockReturnValueOnce()` / `.mockReturnValue()` are called. The priority of which to use is based on what is the last call:
-- if the last call is mockReturnValueOnce() or mockReturnValue(), use the specific return value or default return value. If specific return values are used up or no default return value is set, fall back to try mockImplementation();
-- if the last call is mockImplementationOnce() or mockImplementation(), run the specific implementation and return the result or run default implementation and return the result.
+- if the last call is `.mockReturnValueOnce()` or `.mockReturnValue()`, use the specific return value or default return value. If specific return values are used up or no default return value is set, fall back to try `.mockImplementation()`;
+- if the last call is `.mockImplementationOnce()` or `.mockImplementation()`, run the specific implementation and return the result or run default implementation and return the result.
diff --git a/packages/jest-mock/__typetests__/mock-functions.test.ts b/packages/jest-mock/__typetests__/mock-functions.test.ts
index 6a8905a901d2..e1ace1d254f4 100644
--- a/packages/jest-mock/__typetests__/mock-functions.test.ts
+++ b/packages/jest-mock/__typetests__/mock-functions.test.ts
@@ -15,30 +15,51 @@ import {Mock, SpyInstance, fn, spyOn} from 'jest-mock';
// jest.fn()
-expectType, []>>(
+expectType Promise>>(
fn(async () => 'value')
.mockClear()
.mockReset()
- .mockImplementation(fn())
- .mockImplementationOnce(fn())
+ .mockImplementation(fn(async () => 'value'))
+ .mockImplementationOnce(fn(async () => 'value'))
.mockName('mock')
- .mockReturnThis()
- .mockReturnValue(Promise.resolve('value'))
- .mockReturnValueOnce(Promise.resolve('value'))
.mockResolvedValue('value')
.mockResolvedValueOnce('value')
.mockRejectedValue('error')
- .mockRejectedValue('error'),
+ .mockRejectedValueOnce('error')
+ .mockReturnThis()
+ .mockReturnValue(Promise.resolve('value'))
+ .mockReturnValueOnce(Promise.resolve('value')),
+);
+
+expectType string>>(
+ fn(() => 'value')
+ .mockClear()
+ .mockReset()
+ .mockImplementation(() => 'value')
+ .mockImplementationOnce(() => 'value')
+ .mockName('mock')
+ .mockReturnThis()
+ .mockReturnValue('value')
+ .mockReturnValueOnce('value'),
);
+expectError(fn(() => 'value').mockReturnValue(Promise.resolve('value')));
+expectError(fn(() => 'value').mockReturnValueOnce(Promise.resolve('value')));
+
+expectError(fn(() => 'value').mockResolvedValue('value'));
+expectError(fn(() => 'value').mockResolvedValueOnce('value'));
+
+expectError(fn(() => 'value').mockRejectedValue('error'));
+expectError(fn(() => 'value').mockRejectedValueOnce('error'));
+
expectAssignable(fn()); // eslint-disable-line @typescript-eslint/ban-types
-expectType>(fn());
-expectType>(fn(() => {}));
-expectType>(
+expectType) => unknown>>(fn());
+expectType void>>(fn(() => {}));
+expectType boolean>>(
fn((a: string, b?: number) => true),
);
-expectType>(
+expectType never>>(
fn((e: any) => {
throw new Error();
}),
@@ -63,7 +84,7 @@ const MockObject = fn((credentials: string) => ({
}));
expectType<{
- connect(): Mock>;
+ connect(): Mock<(...args: Array) => unknown>;
disconnect(): void;
}>(new MockObject('credentials'));
expectError(new MockObject());
@@ -92,7 +113,7 @@ expectType>(mockFn.mock.invocationCallOrder);
expectType<
Array<{
- connect(): Mock>;
+ connect(): Mock<(...args: Array) => unknown>;
disconnect(): void;
}>
>(MockObject.mock.instances);
@@ -114,12 +135,12 @@ if (returnValue.type === 'throw') {
expectType(returnValue.value);
}
-expectType>(
+expectType boolean>>(
mockFn.mockClear(),
);
expectError(mockFn.mockClear('some-mock'));
-expectType>(
+expectType boolean>>(
mockFn.mockReset(),
);
expectError(mockFn.mockClear('some-mock'));
@@ -127,7 +148,7 @@ expectError(mockFn.mockClear('some-mock'));
expectType(mockFn.mockRestore());
expectError(mockFn.mockClear('some-mock'));
-expectType>(
+expectType boolean>>(
mockFn.mockImplementation((a, b) => {
expectType(a);
expectType(b);
@@ -138,7 +159,7 @@ expectError(mockFn.mockImplementation((a: number) => false));
expectError(mockFn.mockImplementation(a => 'false'));
expectError(mockFn.mockImplementation());
-expectType, [p: boolean]>>(
+expectType Promise>>(
mockAsyncFn.mockImplementation(async a => {
expectType(a);
return 'mock value';
@@ -146,7 +167,7 @@ expectType, [p: boolean]>>(
);
expectError(mockAsyncFn.mockImplementation(a => 'mock value'));
-expectType>(
+expectType boolean>>(
mockFn.mockImplementationOnce((a, b) => {
expectType(a);
expectType(b);
@@ -157,7 +178,7 @@ expectError(mockFn.mockImplementationOnce((a: number) => false));
expectError(mockFn.mockImplementationOnce(a => 'false'));
expectError(mockFn.mockImplementationOnce());
-expectType, [p: boolean]>>(
+expectType Promise>>(
mockAsyncFn.mockImplementationOnce(async a => {
expectType(a);
return 'mock value';
@@ -165,63 +186,63 @@ expectType, [p: boolean]>>(
);
expectError(mockAsyncFn.mockImplementationOnce(a => 'mock value'));
-expectType>(
+expectType boolean>>(
mockFn.mockName('mockedFunction'),
);
expectError(mockFn.mockName(123));
expectError(mockFn.mockName());
-expectType>(
+expectType boolean>>(
mockFn.mockReturnThis(),
);
expectError(mockFn.mockReturnThis('this'));
-expectType>(
+expectType boolean>>(
mockFn.mockReturnValue(false),
);
expectError(mockFn.mockReturnValue('true'));
expectError(mockFn.mockReturnValue());
-expectType, [p: boolean]>>(
+expectType Promise>>(
mockAsyncFn.mockReturnValue(Promise.resolve('mock value')),
);
expectError(mockAsyncFn.mockReturnValue(Promise.resolve(true)));
-expectType>(
+expectType boolean>>(
mockFn.mockReturnValueOnce(false),
);
expectError(mockFn.mockReturnValueOnce('true'));
expectError(mockFn.mockReturnValueOnce());
-expectType, [p: boolean]>>(
+expectType Promise>>(
mockAsyncFn.mockReturnValueOnce(Promise.resolve('mock value')),
);
expectError(mockAsyncFn.mockReturnValueOnce(Promise.resolve(true)));
-expectType, []>>(
+expectType Promise>>(
fn(() => Promise.resolve('')).mockResolvedValue('Mock value'),
);
expectError(fn(() => Promise.resolve('')).mockResolvedValue(123));
expectError(fn(() => Promise.resolve('')).mockResolvedValue());
-expectType, []>>(
+expectType Promise>>(
fn(() => Promise.resolve('')).mockResolvedValueOnce('Mock value'),
);
expectError(fn(() => Promise.resolve('')).mockResolvedValueOnce(123));
expectError(fn(() => Promise.resolve('')).mockResolvedValueOnce());
-expectType, []>>(
+expectType Promise>>(
fn(() => Promise.resolve('')).mockRejectedValue(new Error('Mock error')),
);
-expectType, []>>(
+expectType Promise>>(
fn(() => Promise.resolve('')).mockRejectedValue('Mock error'),
);
expectError(fn(() => Promise.resolve('')).mockRejectedValue());
-expectType, []>>(
+expectType Promise>>(
fn(() => Promise.resolve('')).mockRejectedValueOnce(new Error('Mock error')),
);
-expectType, []>>(
+expectType Promise>>(
fn(() => Promise.resolve('')).mockRejectedValueOnce('Mock error'),
);
expectError(fn(() => Promise.resolve('')).mockRejectedValueOnce());
@@ -261,22 +282,28 @@ expectNotAssignable(spy); // eslint-disable-line @typescript-eslint/ba
expectError(spy());
expectError(new spy());
-expectType>(spyOn(spiedObject, 'methodA'));
-expectType>(
+expectType>(
+ spyOn(spiedObject, 'methodA'),
+);
+expectType>(
spyOn(spiedObject, 'methodB'),
);
-expectType>(spyOn(spiedObject, 'methodC'));
+expectType>(
+ spyOn(spiedObject, 'methodC'),
+);
-expectType>(spyOn(spiedObject, 'propertyB', 'get'));
-expectType>(
+expectType boolean>>(spyOn(spiedObject, 'propertyB', 'get'));
+expectType void>>(
spyOn(spiedObject, 'propertyB', 'set'),
);
expectError(spyOn(spiedObject, 'propertyB'));
expectError(spyOn(spiedObject, 'methodB', 'get'));
expectError(spyOn(spiedObject, 'methodB', 'set'));
-expectType>(spyOn(spiedObject, 'propertyA', 'get'));
-expectType>(spyOn(spiedObject, 'propertyA', 'set'));
+expectType string>>(spyOn(spiedObject, 'propertyA', 'get'));
+expectType void>>(
+ spyOn(spiedObject, 'propertyA', 'set'),
+);
expectError(spyOn(spiedObject, 'propertyA'));
expectError(spyOn(spiedObject, 'notThere'));
@@ -286,17 +313,17 @@ expectError(spyOn(true, 'methodA'));
expectError(spyOn(spiedObject));
expectError(spyOn());
-expectType>(
+expectType boolean>>(
spyOn(spiedArray as unknown as ArrayConstructor, 'isArray'),
);
expectError(spyOn(spiedArray, 'isArray'));
-expectType>(
+expectType string>>(
spyOn(spiedFunction as unknown as Function, 'toString'), // eslint-disable-line @typescript-eslint/ban-types
);
expectError(spyOn(spiedFunction, 'toString'));
-expectType>(
+expectType Date>>(
spyOn(globalThis, 'Date'),
);
-expectType>(spyOn(Date, 'now'));
+expectType number>>(spyOn(Date, 'now'));
diff --git a/packages/jest-mock/__typetests__/utility-types.test.ts b/packages/jest-mock/__typetests__/utility-types.test.ts
index af4efdb60a49..3b866f801e49 100644
--- a/packages/jest-mock/__typetests__/utility-types.test.ts
+++ b/packages/jest-mock/__typetests__/utility-types.test.ts
@@ -7,9 +7,9 @@
import {expectAssignable, expectNotAssignable, expectType} from 'tsd-lite';
import type {
- ConstructorLike,
+ ClassLike,
ConstructorLikeKeys,
- MethodLike,
+ FunctionLike,
MethodLikeKeys,
PropertyLikeKeys,
} from 'jest-mock';
@@ -58,27 +58,27 @@ type SomeObject = typeof someObject;
// ClassLike
-expectAssignable(SomeClass);
-expectNotAssignable(() => {});
-expectNotAssignable(function abc() {
+expectAssignable(SomeClass);
+expectNotAssignable(() => {});
+expectNotAssignable(function abc() {
return;
});
-expectNotAssignable('abc');
-expectNotAssignable(123);
-expectNotAssignable(false);
-expectNotAssignable(someObject);
+expectNotAssignable('abc');
+expectNotAssignable(123);
+expectNotAssignable(false);
+expectNotAssignable(someObject);
// FunctionLike
-expectAssignable(() => {});
-expectAssignable(function abc() {
+expectAssignable(() => {});
+expectAssignable(function abc() {
return;
});
-expectNotAssignable('abc');
-expectNotAssignable(123);
-expectNotAssignable(false);
-expectNotAssignable(SomeClass);
-expectNotAssignable(someObject);
+expectNotAssignable('abc');
+expectNotAssignable(123);
+expectNotAssignable(false);
+expectNotAssignable(SomeClass);
+expectNotAssignable(someObject);
// ConstructorKeys
diff --git a/packages/jest-mock/src/index.ts b/packages/jest-mock/src/index.ts
index 32e7cdb8a164..e9b295f84b82 100644
--- a/packages/jest-mock/src/index.ts
+++ b/packages/jest-mock/src/index.ts
@@ -18,36 +18,34 @@ export type MockFunctionMetadataType =
| 'undefined';
export type MockFunctionMetadata<
- T,
- Y extends Array,
+ T extends UnknownFunction = UnknownFunction,
MetadataType = MockFunctionMetadataType,
> = {
ref?: number;
- members?: Record>;
- mockImpl?: (...args: Y) => T;
+ members?: Record>;
+ mockImpl?: FunctionType;
name?: string;
refID?: number;
type?: MetadataType;
- value?: T;
+ value?: ReturnType;
length?: number;
};
-export type ConstructorLike = {new (...args: Array): any};
-
-export type MethodLike = (...args: Array) => any;
+export type ClassLike = {new (...args: any): any};
+export type FunctionLike = (...args: any) => any;
export type ConstructorLikeKeys = {
- [K in keyof T]: T[K] extends ConstructorLike ? K : never;
+ [K in keyof T]: T[K] extends ClassLike ? K : never;
}[keyof T];
export type MethodLikeKeys = {
- [K in keyof T]: T[K] extends MethodLike ? K : never;
+ [K in keyof T]: T[K] extends FunctionLike ? K : never;
}[keyof T];
export type PropertyLikeKeys = {
- [K in keyof T]: T[K] extends MethodLike
+ [K in keyof T]: T[K] extends FunctionLike
? never
- : T[K] extends ConstructorLike
+ : T[K] extends ClassLike
? never
: K;
}[keyof T];
@@ -61,99 +59,114 @@ type ConstructorParameters = T extends new (...args: infer P) => any
export type MaybeMockedConstructor = T extends new (
...args: Array
) => infer R
- ? MockInstance>
+ ? MockInstance<(...args: ConstructorParameters) => R>
: T;
-export interface MockWithArgs
- extends MockInstance, Parameters> {
+export interface MockWithArgs
+ extends MockInstance> {
new (...args: ConstructorParameters): T;
(...args: Parameters): ReturnType;
}
-export type MockedFunction = MockWithArgs & {
+export type MockedFunction = MockWithArgs & {
[K in keyof T]: T[K];
};
-export type MockedFunctionDeep = MockWithArgs &
+export type MockedFunctionDeep = MockWithArgs &
MockedObjectDeep;
export type MockedObject = MaybeMockedConstructor & {
- [K in MethodLikeKeys]: T[K] extends MethodLike
+ [K in MethodLikeKeys]: T[K] extends FunctionLike
? MockedFunction
: T[K];
} & {[K in PropertyLikeKeys]: T[K]};
export type MockedObjectDeep = MaybeMockedConstructor & {
- [K in MethodLikeKeys]: T[K] extends MethodLike
+ [K in MethodLikeKeys]: T[K] extends FunctionLike
? MockedFunctionDeep
: T[K];
} & {[K in PropertyLikeKeys]: MaybeMockedDeep};
-export type MaybeMocked = T extends MethodLike
+export type MaybeMocked = T extends FunctionLike
? MockedFunction
: T extends object
? MockedObject
: T;
-export type MaybeMockedDeep = T extends MethodLike
+export type MaybeMockedDeep = T extends FunctionLike
? MockedFunctionDeep
: T extends object
? MockedObjectDeep
: T;
export type Mocked = {
- [P in keyof T]: T[P] extends MethodLike
- ? MockInstance, Parameters>
- : T[P] extends ConstructorLike
+ [P in keyof T]: T[P] extends FunctionLike
+ ? MockInstance>
+ : T[P] extends ClassLike
? MockedClass
: T[P];
} & T;
-export type MockedClass = MockInstance<
- InstanceType,
- T extends new (...args: infer P) => any ? P : never
+export type MockedClass = MockInstance<
+ (args: T extends new (...args: infer P) => any ? P : never) => InstanceType
> & {
prototype: T extends {prototype: any} ? Mocked : never;
} & T;
-export interface Mock = Array>
+type UnknownFunction = (...args: Array) => unknown;
+
+/**
+ * All what the internal typings need is to be sure that we have any-function.
+ * `FunctionLike` type ensures that and helps to constrain the type as well.
+ * The default of `UnknownFunction` makes sure that `any`s do not leak to the
+ * user side. For instance, calling `fn()` without implementation will return
+ * a mock of `(...args: Array) => unknown` type. If implementation
+ * is provided, its typings are inferred correctly.
+ */
+export interface Mock
extends Function,
- MockInstance {
- new (...args: Y): T;
- (...args: Y): T;
+ MockInstance {
+ new (...args: Parameters): ReturnType;
+ (...args: Parameters): ReturnType;
}
-// TODO Replace with Awaited utility type when minimum supported TS version will be 4.5 or above
-//https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-5.html#the-awaited-type-and-promise-improvements
-type Unpromisify = T extends Promise ? R : never;
+type FunctionType = (
+ ...args: Parameters
+) => ReturnType;
+
+type ResolveType = ReturnType extends PromiseLike<
+ infer U
+>
+ ? U
+ : never;
-export interface MockInstance> {
+type RejectType = ReturnType extends PromiseLike
+ ? unknown
+ : never;
+
+export interface MockInstance {
_isMockFunction: true;
_protoImpl: Function;
- getMockImplementation(): ((...args: Y) => T) | undefined;
+ getMockImplementation(): FunctionType | undefined;
getMockName(): string;
- mock: MockFunctionState;
+ mock: MockFunctionState;
mockClear(): this;
mockReset(): this;
mockRestore(): void;
- mockImplementation(fn: (...args: Y) => T): this;
- /** @internal */
- mockImplementation(fn: () => Promise): this;
- mockImplementationOnce(fn: (...args: Y) => T): this;
- /** @internal */
- mockImplementationOnce(fn: () => Promise): this;
+ mockImplementation(fn: FunctionType): this;
+ mockImplementationOnce(fn: FunctionType): this;
mockName(name: string): this;
mockReturnThis(): this;
- mockReturnValue(value: T): this;
- mockReturnValueOnce(value: T): this;
- mockResolvedValue(value: Unpromisify): this;
- mockResolvedValueOnce(value: Unpromisify): this;
- mockRejectedValue(value: unknown): this;
- mockRejectedValueOnce(value: unknown): this;
+ mockReturnValue(value: ReturnType): this;
+ mockReturnValueOnce(value: ReturnType): this;
+ mockResolvedValue(value: ResolveType): this;
+ mockResolvedValueOnce(value: ResolveType): this;
+ mockRejectedValue(value: RejectType): this;
+ mockRejectedValueOnce(value: RejectType): this;
}
-export interface SpyInstance>
- extends MockInstance {}
+export interface SpyInstance
+ extends MockInstance {}
type MockFunctionResultIncomplete = {
type: 'incomplete';
@@ -164,12 +177,12 @@ type MockFunctionResultIncomplete = {
*/
value: undefined;
};
-type MockFunctionResultReturn = {
+type MockFunctionResultReturn = {
type: 'return';
/**
* Result of a single call to a mock function that returned.
*/
- value: T;
+ value: ReturnType;
};
type MockFunctionResultThrow = {
type: 'throw';
@@ -179,20 +192,20 @@ type MockFunctionResultThrow = {
value: unknown;
};
-type MockFunctionResult =
+type MockFunctionResult =
| MockFunctionResultIncomplete
| MockFunctionResultReturn
| MockFunctionResultThrow;
-type MockFunctionState> = {
+type MockFunctionState = {
/**
* List of the call arguments of all calls that have been made to the mock.
*/
- calls: Array;
+ calls: Array>;
/**
* List of all the object instances that have been instantiated from the mock.
*/
- instances: Array;
+ instances: Array>;
/**
* List of the call order indexes of the mock. Jest is indexing the order of
* invocations of all mocks in a test file. The index is starting with `1`.
@@ -202,7 +215,7 @@ type MockFunctionState> = {
* List of the call arguments of the last call that was made to the mock.
* If the function was not called, it will return `undefined`.
*/
- lastCall?: Y;
+ lastCall?: Parameters;
/**
* List of the results of all calls that have been made to the mock.
*/
@@ -431,7 +444,7 @@ function getType(ref?: unknown): MockFunctionMetadataType | null {
}
}
-function isReadonlyProp(object: any, prop: string): boolean {
+function isReadonlyProp(object: unknown, prop: string): boolean {
if (
prop === 'arguments' ||
prop === 'caller' ||
@@ -462,7 +475,7 @@ function isReadonlyProp(object: any, prop: string): boolean {
export class ModuleMocker {
private _environmentGlobal: typeof globalThis;
- private _mockState: WeakMap, MockFunctionState>;
+ private _mockState: WeakMap;
private _mockConfigRegistry: WeakMap;
private _spyState: Set<() => void>;
private _invocationCallCounter: number;
@@ -526,9 +539,7 @@ export class ModuleMocker {
return Array.from(slots);
}
- private _ensureMockConfig