Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(jest-mock)!: simplify usage of jest.fn generic type arguments #12489

Merged
merged 24 commits into from Feb 26, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 7 additions & 1 deletion docs/JestObjectAPI.md
Expand Up @@ -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.

Expand All @@ -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.
Expand Down
79 changes: 45 additions & 34 deletions docs/MockFunctionAPI.md
Expand Up @@ -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'`:
SimenB marked this conversation as resolved.
Show resolved Hide resolved

```ts
import {jest} from '@jest/globals';
```

:::

## Methods

import TOCInline from "@theme/TOCInline"
Expand Down Expand Up @@ -258,8 +268,10 @@ expect(mockFn).toHaveBeenCalled();

Will result in this error:

```bash
```
expect(mockedFunction).toHaveBeenCalled()

Expected mock function "mockedFunction" to have been called, but it was not called.
```

### `mockFn.mockReturnThis()`
Expand Down Expand Up @@ -516,19 +528,49 @@ test('async test', async () => {
</TabItem>
</Tabs>

## TypeScript
## TypeScript Usage

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.fn(implementation?)`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, I double checked if the example works. All is correct. Will add it to examples folder later. TypeScript example will need more changes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

convert back to draft so I don't accidentally merge? 😀


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
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<typeof add>();

// `.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
// was a complex function we are mocking.
return a + b;
});

// `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.MockedFunction`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we remove this as well? comes from @types/jest and not us

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. I took it out and will bring it back after jest.Mocked refactor. What about that note on Create React App? Just above?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, kill it. only thing under the TypeScript heading should be either pointing to "getting started" or examples using jest from @jest/globals. All the other stuff (CRA, "see this repo", "jest is written in TS") should go, IMO


> `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).
The following examples will assume you have an understanding of how [Jest mock functions work with JavaScript](MockFunctions.md)

You can use `jest.MockedFunction` to represent a function that has been replaced by a Jest mock.

Expand All @@ -552,37 +594,6 @@ test('calculate calls add', () => {
});
```

Example using [`jest.fn`](JestObjectAPI.md#jestfnimplementation):

```ts
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<typeof add>();

// 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`
// and that the returned value is a `number`.
mockAdd.mockImplementation((a, b) => {
// Yes, this mock is still adding two numbers but imagine this
// was a complex function we are mocking.
return a + b;
});

// `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`.
Expand Down
22 changes: 15 additions & 7 deletions 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
Expand All @@ -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.

Expand Down Expand Up @@ -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:

Expand All @@ -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.