Skip to content

Commit 7d8c313

Browse files
authored
refactor(jest-mock)!: change the default jest.mocked helper’s behaviour to deep mocked (#13125)
1 parent 2f4340c commit 7d8c313

File tree

8 files changed

+129
-67
lines changed

8 files changed

+129
-67
lines changed

.eslintrc.cjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ module.exports = {
180180
},
181181
},
182182
{
183-
files: ['website/**/*'],
183+
files: ['docs/**/*', 'website/**/*'],
184184
rules: {
185185
'import/order': 'off',
186186
'import/sort-keys': 'off',

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- `[jest-environment-jsdom]` [**BREAKING**] Upgrade to `jsdom@20` ([#13037](https://github.com/facebook/jest/pull/13037), [#13058](https://github.com/facebook/jest/pull/13058))
77
- `[@jest/globals]` Add `jest.Mocked`, `jest.MockedClass`, `jest.MockedFunction` and `jest.MockedObject` utility types ([#12727](https://github.com/facebook/jest/pull/12727))
88
- `[jest-mock]` [**BREAKING**] Refactor `Mocked*` utility types. `MaybeMockedDeep` and `MaybeMocked` became `Mocked` and `MockedShallow` respectively; only deep mocked variants of `MockedClass`, `MockedFunction` and `MockedObject` are exported ([#13123](https://github.com/facebook/jest/pull/13123), [#13124](https://github.com/facebook/jest/pull/13124))
9+
- `[jest-mock]` [**BREAKING**] Change the default `jest.mocked` helper’s behavior to deep mocked ([#13125](https://github.com/facebook/jest/pull/13125))
910
- `[jest-worker]` Adds `workerIdleMemoryLimit` option which is used as a check for worker memory leaks >= Node 16.11.0 and recycles child workers as required. ([#13056](https://github.com/facebook/jest/pull/13056), [#13105](https://github.com/facebook/jest/pull/13105), [#13106](https://github.com/facebook/jest/pull/13106), [#13107](https://github.com/facebook/jest/pull/13107))
1011
- `[pretty-format]` [**BREAKING**] Remove `ConvertAnsi` plugin in favour of `jest-serializer-ansi-escapes` ([#13040](https://github.com/facebook/jest/pull/13040))
1112

docs/JestObjectAPI.md

Lines changed: 15 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,20 @@ Modules that are mocked with `jest.mock` are mocked only for the file that calls
282282

283283
Returns the `jest` object for chaining.
284284

285+
:::tip
286+
287+
Writing tests in TypeScript? Use [`jest.Mocked`](MockFunctionAPI.md/#jestmockedsource) utility type or [`jest.mocked()`](MockFunctionAPI.md/#jestmockedsource-options) helper method to have your mocked modules typed.
288+
289+
:::
290+
291+
### `jest.Mocked<Source>`
292+
293+
See [TypeScript Usage](MockFunctionAPI.md/#jestmockedsource) chapter of Mock Functions page for documentation.
294+
295+
### `jest.mocked(source, options?)`
296+
297+
See [TypeScript Usage](MockFunctionAPI.md/#jestmockedsource-options) chapter of Mock Functions page for documentation.
298+
285299
### `jest.unmock(moduleName)`
286300

287301
Indicates that the module system should never return a mocked version of the specified module from `require()` (e.g. that it should always return the real module).
@@ -467,7 +481,7 @@ const returnsTrue = jest.fn(() => true);
467481
console.log(returnsTrue()); // true;
468482
```
469483

470-
:::note
484+
:::tip
471485

472486
See [Mock Functions](MockFunctionAPI.md#jestfnimplementation) page for details on TypeScript usage.
473487

@@ -598,50 +612,6 @@ Returns the `jest` object for chaining.
598612

599613
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.
600614

601-
### `jest.mocked<T>(item: T, deep = false)`
602-
603-
The `mocked` test helper provides typings on your mocked modules and even their deep methods, based on the typing of its source. It makes use of the latest TypeScript feature, so you even have argument types completion in the IDE (as opposed to `jest.MockInstance`).
604-
605-
_Note: while it needs to be a function so that input type is changed, the helper itself does nothing else than returning the given input value._
606-
607-
Example:
608-
609-
```ts
610-
// foo.ts
611-
export const foo = {
612-
a: {
613-
b: {
614-
c: {
615-
hello: (name: string) => `Hello, ${name}`,
616-
},
617-
},
618-
},
619-
name: () => 'foo',
620-
};
621-
```
622-
623-
```ts
624-
// foo.spec.ts
625-
import {foo} from './foo';
626-
jest.mock('./foo');
627-
628-
// here the whole foo var is mocked deeply
629-
const mockedFoo = jest.mocked(foo, true);
630-
631-
test('deep', () => {
632-
// there will be no TS error here, and you'll have completion in modern IDEs
633-
mockedFoo.a.b.c.hello('me');
634-
// same here
635-
expect(mockedFoo.a.b.c.hello.mock.calls).toHaveLength(1);
636-
});
637-
638-
test('direct', () => {
639-
foo.name();
640-
// here only foo.name is mocked (or its methods if it's an object)
641-
expect(jest.mocked(foo.name).mock.calls).toHaveLength(1);
642-
});
643-
```
644-
645615
## Fake Timers
646616

647617
### `jest.useFakeTimers(fakeTimersConfig?)`

docs/MockFunctionAPI.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,13 +526,17 @@ test('calculate calls add', () => {
526526
The `jest.Mocked<Source>` utility type returns the `Source` type wrapped with type definitions of Jest mock function.
527527

528528
```ts
529-
import fetch from 'node-fetch';
530529
import {expect, jest, test} from '@jest/globals';
530+
import type {fetch} from 'node-fetch';
531531

532532
jest.mock('node-fetch');
533533

534534
let mockedFetch: jest.Mocked<typeof fetch>;
535535

536+
afterEach(() => {
537+
mockedFetch.mockClear();
538+
});
539+
536540
test('makes correct call', () => {
537541
mockedFetch = getMockedFetch();
538542
// ...
@@ -545,3 +549,50 @@ test('returns correct data', () => {
545549
```
546550

547551
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>`.
552+
553+
### `jest.mocked(source, options?)`
554+
555+
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.
556+
557+
Returns the `source` object.
558+
559+
```ts title="song.ts"
560+
export const song = {
561+
one: {
562+
more: {
563+
time: (t: number) => {
564+
return t;
565+
},
566+
},
567+
},
568+
};
569+
```
570+
571+
```ts title="song.test.ts"
572+
import {expect, jest, test} from '@jest/globals';
573+
import {song} from './song';
574+
575+
jest.mock('./song');
576+
jest.spyOn(console, 'log');
577+
578+
const mockedSong = jest.mocked(song);
579+
// or through `jest.Mocked<Source>`
580+
// const mockedSong = song as jest.Mocked<typeof song>;
581+
582+
test('deep method is typed correctly', () => {
583+
mockedSong.one.more.time.mockReturnValue(12);
584+
585+
expect(mockedSong.one.more.time(10)).toBe(12);
586+
expect(mockedSong.one.more.time.mock.calls).toHaveLength(1);
587+
});
588+
589+
test('direct usage', () => {
590+
jest.mocked(console.log).mockImplementation(() => {
591+
return;
592+
});
593+
594+
console.log('one more time');
595+
596+
expect(jest.mocked(console.log).mock.calls).toHaveLength(1);
597+
});
598+
```

docs/UpgradingToJest29.md

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,38 @@ If you want to keep the old behavior, you can set the `snapshotFormat` property
4040

4141
Notably, `jsdom@20` includes support for `crypto.getRandomValues()`, which means packages like `jsdom` and `nanoid`, which doesn't work properly in Jest@28, can work without extra polyfills.
4242

43-
## `jest-mock`
43+
## `pretty-format`
4444

45-
Exports of `Mocked*` utility types changed. `MaybeMockedDeep` and `MaybeMocked` now are exported as `Mocked` and `MockedShallow` respectively; only deep mocked variants of `MockedClass`, `MockedFunction` and `MockedObject` are exposed.
45+
`ConvertAnsi` plugin is removed from `pretty-format` package in favour of [`jest-serializer-ansi-escapes`](https://github.com/mrazauskas/jest-serializer-ansi-escapes).
4646

47-
## `pretty-format`
47+
### `jest-mock`
48+
49+
Exports of `Mocked*` utility types from `jest-mock` package have changed. `MaybeMockedDeep` and `MaybeMocked` now are exported as `Mocked` and `MockedShallow` respectively; only deep mocked variants of `MockedClass`, `MockedFunction` and `MockedObject` are exposed.
50+
51+
## TypeScript
52+
53+
:::info
54+
55+
The TypeScript examples from this page will only work as documented if you import `jest` from `'@jest/globals'`:
4856

49-
`ConvertAnsi` plugin is removed in favour of [`jest-serializer-ansi-escapes`](https://github.com/mrazauskas/jest-serializer-ansi-escapes).
57+
```ts
58+
import {jest} from '@jest/globals';
59+
```
60+
61+
:::
62+
63+
### `jest.mocked()`
64+
65+
The [`jest.mocked()`](MockFunctionAPI.md/#jestmockedsource-options) helper method now wraps types of deep members of passed object by default. If you have used the method with `true` as the second argument, remove it to avoid type errors:
66+
67+
```diff
68+
- const mockedObject = jest.mocked(someObject, true);
69+
+ const mockedObject = jest.mocked(someObject);
70+
```
71+
72+
To have the old shallow mocked behavior, pass `{shallow: true}` as the second argument:
73+
74+
```diff
75+
- const mockedObject = jest.mocked(someObject);
76+
+ const mockedObject = jest.mocked(someObject, {shallow: true});
77+
```

packages/jest-environment/src/index.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,12 @@ export interface Jest {
201201
```
202202
*/
203203
requireActual: (moduleName: string) => unknown;
204+
/**
205+
* Wraps types of the `source` object and its deep members with type definitions
206+
* of Jest mock function. Pass `{shallow: true}` option to disable the deeply
207+
* mocked behavior.
208+
*/
209+
mocked: ModuleMocker['mocked'];
204210
/**
205211
* Returns a mock module instead of the actual module, bypassing all checks
206212
* on whether the module should be required normally or not.
@@ -224,10 +230,6 @@ export interface Jest {
224230
* with `jest.spyOn()`; other mocks will require you to manually restore them.
225231
*/
226232
restoreAllMocks(): Jest;
227-
/**
228-
* Wraps an object or a module with mock type definitions.
229-
*/
230-
mocked: ModuleMocker['mocked'];
231233
/**
232234
* Runs failed tests n-times until they pass or until the max number of
233235
* retries is exhausted.

packages/jest-mock/src/index.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,13 +1216,16 @@ export class ModuleMocker {
12161216
return value == null ? `${value}` : typeof value;
12171217
}
12181218

1219-
mocked<T extends object>(item: T, deep?: false): MockedShallow<T>;
1220-
mocked<T extends object>(item: T, deep: true): Mocked<T>;
1219+
mocked<T extends object>(source: T, options?: {shallow: false}): Mocked<T>;
12211220
mocked<T extends object>(
1222-
item: T,
1223-
_deep = false,
1221+
source: T,
1222+
options: {shallow: true},
1223+
): MockedShallow<T>;
1224+
mocked<T extends object>(
1225+
source: T,
1226+
_options?: {shallow: boolean},
12241227
): Mocked<T> | MockedShallow<T> {
1225-
return item as Mocked<T> | MockedShallow<T>;
1228+
return source as Mocked<T> | MockedShallow<T>;
12261229
}
12271230
}
12281231

packages/jest-types/__typetests__/jest.test.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
MockedClass,
1414
MockedFunction,
1515
MockedObject,
16+
MockedShallow,
1617
ModuleMocker,
1718
SpyInstance,
1819
} from 'jest-mock';
@@ -276,11 +277,19 @@ expectType<MockedObject<typeof someObject>>(
276277
someObject as jest.MockedObject<typeof someObject>,
277278
);
278279

279-
// deep mocked()
280+
// mocked()
280281

281-
const mockObjectA = jest.mocked(someObject, true);
282+
expectType<Mocked<typeof someObject>>(jest.mocked(someObject));
283+
expectType<Mocked<typeof someObject>>(
284+
jest.mocked(someObject, {shallow: false}),
285+
);
286+
expectType<MockedShallow<typeof someObject>>(
287+
jest.mocked(someObject, {shallow: true}),
288+
);
282289

283-
expectError(jest.mocked('abc', true));
290+
expectError(jest.mocked('abc'));
291+
292+
const mockObjectA = jest.mocked(someObject);
284293

285294
expectType<[]>(mockObjectA.methodA.mock.calls[0]);
286295
expectType<[b: string]>(mockObjectA.methodB.mock.calls[0]);
@@ -333,11 +342,9 @@ expectError(
333342

334343
expectAssignable<typeof someObject>(mockObjectA);
335344

336-
// mocked()
337-
338-
const mockObjectB = jest.mocked(someObject);
345+
// shallow mocked()
339346

340-
expectError(jest.mocked('abc'));
347+
const mockObjectB = jest.mocked(someObject, {shallow: true});
341348

342349
expectType<[]>(mockObjectB.methodA.mock.calls[0]);
343350
expectType<[b: string]>(mockObjectB.methodB.mock.calls[0]);

0 commit comments

Comments
 (0)