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

Add docs for using mocks in TypeScript #10415

Merged
merged 9 commits into from Oct 29, 2020
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -13,6 +13,7 @@

### Chore & Maintenance

- `[docs]` Add docs for using mocks in TypeScript([#10415](https://github.com/facebook/jest/pull/10415))
sheepsteak marked this conversation as resolved.
Show resolved Hide resolved
- `[jest-cli]` chore: standardize files and folder names ([#10698](https://github.com/facebook/jest/pull/1098))

### Performance
Expand Down
8 changes: 8 additions & 0 deletions docs/GettingStarted.md
Expand Up @@ -171,3 +171,11 @@ module.exports = {
```

However, there are some [caveats](https://babeljs.io/docs/en/babel-plugin-transform-typescript#caveats) to using TypeScript with Babel. Because TypeScript support in Babel is purely transpilation, Jest will not type-check your tests as they are run. If you want that, you can use [ts-jest](https://github.com/kulshekhar/ts-jest) instead, or just run the TypeScript compiler [tsc](https://www.typescriptlang.org/docs/handbook/compiler-options.html) separately (or as part of your build process).

You may also want to install the [`@types/jest`](https://www.npmjs.com/package/@types/jest) module for the version of Jest you're using. This will help provide full typing [when writing your tests with TypeScript](TypeScript.md).

> For `@types/*` modules it's recommended to try to match the version of the associated module. For example, if you are using `26.4.0` of `jest` then using `26.4.x` of `@types/jest` is ideal. In general, try to match the major (`26`) and minor (`4`) version as closely as possible.

```bash
yarn add --dev @types/jest
```
130 changes: 130 additions & 0 deletions docs/MockFunctionAPI.md
Expand Up @@ -316,3 +316,133 @@ test('async test', async () => {
await asyncMock(); // throws "Async error"
});
```

## 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](https://jestjs.io/docs/en/getting-started#using-typescript) guide for to get setup with TypeScript.
SimenB marked this conversation as resolved.
Show resolved Hide resolved

You can see an example of using Jest with TypeScript in our [GitHub repository](https://github.com/facebook/jest/tree/master/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](https://jestjs.io/docs/en/mock-functions).

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

Example using [automatic `jest.mock`](https://jestjs.io/docs/en/jest-object#jestmockmodulename-factory-options):

```ts
// Assume `add` is imported and used within `calculate`.
import add from './add';
import calculate from './calc';

jest.mock('./add');

// Our mock of `add` is now fully typed
const mockAdd = add as jest.MockedFunction<typeof add>;

test('calculate calls add', () => {
calculate('Add', 1, 2);

expect(mockAdd).toBeCalledTimes(1);
expect(mockAdd).toBeCalledWith(1, 2);
});
```

Example using [`jest.fn`](https://jestjs.io/docs/en/jest-object#jestfnimplementation):

```ts
// Here `add` is imported for its type
import 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<typeof add>;

// Note: You can use the `jest.fn` type directly like this if you want:
// const mockAdd = jest.fn<ReturnType<typeof add>, Parameters<typeof add>>();
// `jest.MockedFunction` is a more friendly shortcut.

// Now we can easily set up mock implementations.
// All the `.mock*` API can now give you proper types for `add`.
// https://jestjs.io/docs/en/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`.

The following examples will assume you have an understanding of how [Jest mock classes work with JavaScript](https://jestjs.io/docs/en/es6-class-mocks).

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

Converting the [ES6 Class automatic mock example](https://jestjs.io/docs/en/es6-class-mocks#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<typeof SoundPlayer>;

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);
});
```