From 5819f1d481a308f1637f086668243789da9f9b7e Mon Sep 17 00:00:00 2001 From: Rajat Date: Thu, 25 Nov 2021 22:40:41 +0530 Subject: [PATCH 1/8] mockedfn --- .../jest-mock/src/__tests__/index.test.ts | 9 ++- packages/jest-mock/src/index.ts | 69 +++++++++++++++++-- 2 files changed, 72 insertions(+), 6 deletions(-) diff --git a/packages/jest-mock/src/__tests__/index.test.ts b/packages/jest-mock/src/__tests__/index.test.ts index 854d79a1c7b7..01dd9d067659 100644 --- a/packages/jest-mock/src/__tests__/index.test.ts +++ b/packages/jest-mock/src/__tests__/index.test.ts @@ -9,7 +9,7 @@ /* eslint-disable local/ban-types-eventually, local/prefer-rest-params-eventually */ import vm, {Context} from 'vm'; -import {ModuleMocker, fn, spyOn} from '../'; +import {ModuleMocker, fn, mocked,spyOn} from '../'; describe('moduleMocker', () => { let moduleMocker: ModuleMocker; @@ -1452,6 +1452,13 @@ describe('moduleMocker', () => { }); }); +describe('mocked', () => { + it('should return unmodified input', () => { + const subject = {} + expect(mocked(subject)).toBe(subject) + }) +}); + test('`fn` and `spyOn` do not throw', () => { expect(() => { fn(); diff --git a/packages/jest-mock/src/index.ts b/packages/jest-mock/src/index.ts index 0d0cdce00a8c..1f441b3bb945 100644 --- a/packages/jest-mock/src/index.ts +++ b/packages/jest-mock/src/index.ts @@ -1,3 +1,6 @@ +/* eslint-disable prettier/prettier */ + + /** * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. * @@ -17,11 +20,7 @@ export type MockFunctionMetadataType = | 'null' | 'undefined'; -export type MockFunctionMetadata< - T, - Y extends Array, - Type = MockFunctionMetadataType, -> = { +export type MockFunctionMetadata< T, Y extends Array, Type = MockFunctionMetadataType,> = { ref?: number; members?: Record>; mockImpl?: (...args: Y) => T; @@ -32,6 +31,55 @@ export type MockFunctionMetadata< length?: number; }; +export type MockableFunction = (...args: Array) => any +export type MethodKeysOf = { [K in keyof T]: T[K] extends MockableFunction ? K : never }[keyof T] +export type PropertyKeysOf = { [K in keyof T]: T[K] extends MockableFunction ? never : K }[keyof T] + +export type ArgumentsOf = T extends (...args: infer A) => any ? A : never + +export type ConstructorArgumentsOf = T extends new (...args: infer A) => any ? A : never +export type MaybeMockedConstructor = T extends new (...args:Array) => infer R + ? jest.MockInstance> + : T +export type MockedFunction = MockWithArgs & { [K in keyof T]: T[K] } +export type MockedFunctionDeep = MockWithArgs & MockedObjectDeep +export type MockedObject = MaybeMockedConstructor & { + [K in MethodKeysOf]: T[K] extends MockableFunction ? MockedFunction : T[K] +} & { [K in PropertyKeysOf]: T[K] } +export type MockedObjectDeep = MaybeMockedConstructor & { + [K in MethodKeysOf]: T[K] extends MockableFunction ? MockedFunctionDeep : T[K] +} & { [K in PropertyKeysOf]: MaybeMockedDeep } + +export type MaybeMockedDeep = T extends MockableFunction + ? MockedFunctionDeep + : T extends object + ? MockedObjectDeep + : T + +export type MaybeMocked = T extends MockableFunction ? MockedFunction : T extends object ? MockedObject : T + + +export type Mocked = { + [P in keyof T]: T[P] extends (...args: Array) => any + ? MockInstance, jest.ArgsType> + : T[P] extends jest.Constructable + ? MockedClass + : T[P] +} & + T; + +export type MockedClass = MockInstance< + InstanceType, + T extends new (...args: infer P) => any ? P : never + > & { + prototype: T extends { prototype: any } ? Mocked : never; + } & T; + +export interface MockWithArgs extends jest.MockInstance, ArgumentsOf> { + new (...args: ConstructorArgumentsOf): T + (...args: ArgumentsOf): ReturnType +} + export interface Mock = Array> extends Function, MockInstance { @@ -1109,9 +1157,20 @@ export class ModuleMocker { private _typeOf(value: any): string { return value == null ? '' + value : typeof value; } + + // the typings test helper + mocked(item: T, deep?: false): MaybeMocked + + mocked(item: T, deep: true): MaybeMockedDeep + + mocked(item: T, _deep = false): MaybeMocked | MaybeMockedDeep { + + return item as any + } const JestMock = new ModuleMocker(global as unknown as typeof globalThis); export const fn = JestMock.fn.bind(JestMock); export const spyOn = JestMock.spyOn.bind(JestMock); +export const mocked=JestMock.mocked.bind(JestMock); From e42a1b53779807179b4c0ed5ee5c860253cb0f77 Mon Sep 17 00:00:00 2001 From: Rajat Date: Thu, 25 Nov 2021 22:42:08 +0530 Subject: [PATCH 2/8] changes --- packages/jest-mock/src/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/jest-mock/src/index.ts b/packages/jest-mock/src/index.ts index 1f441b3bb945..e7753edb5149 100644 --- a/packages/jest-mock/src/index.ts +++ b/packages/jest-mock/src/index.ts @@ -1,5 +1,3 @@ -/* eslint-disable prettier/prettier */ - /** * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. From cf05643458cca70422fa13f148e0965efdac248d Mon Sep 17 00:00:00 2001 From: Rajat Date: Thu, 25 Nov 2021 23:14:58 +0530 Subject: [PATCH 3/8] adding curly braces --- packages/jest-mock/src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/jest-mock/src/index.ts b/packages/jest-mock/src/index.ts index e7753edb5149..77b2d8d1e04f 100644 --- a/packages/jest-mock/src/index.ts +++ b/packages/jest-mock/src/index.ts @@ -1166,9 +1166,10 @@ export class ModuleMocker { return item as any } +} const JestMock = new ModuleMocker(global as unknown as typeof globalThis); export const fn = JestMock.fn.bind(JestMock); export const spyOn = JestMock.spyOn.bind(JestMock); -export const mocked=JestMock.mocked.bind(JestMock); +export const mocked= JestMock.mocked.bind(JestMock); From 289a08be09fd33544edb018bbd9a3b8bd0ee57f7 Mon Sep 17 00:00:00 2001 From: Rajat Date: Fri, 26 Nov 2021 01:10:00 +0530 Subject: [PATCH 4/8] Update CHANGELOG.md --- CHANGELOG.md | 1 + packages/jest-mock/src/index.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc106e5172cf..c640ada15d9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - `[jest-core]` Add support for `testResultsProcessor` written in ESM ([#12006](https://github.com/facebook/jest/pull/12006)) - `[jest-diff, pretty-format]` Add `compareKeys` option for custom sorting of object keys ([#11992](https://github.com/facebook/jest/pull/11992)) +- `[jest-mock]` Add `ts-jest` mock util functions ([#12089](https://github.com/facebook/jest/pull/12089)) ### Fixes diff --git a/packages/jest-mock/src/index.ts b/packages/jest-mock/src/index.ts index 77b2d8d1e04f..a0476897b5a2 100644 --- a/packages/jest-mock/src/index.ts +++ b/packages/jest-mock/src/index.ts @@ -29,14 +29,14 @@ export type MockFunctionMetadata< T, Y extends Array, Type = MockFunct length?: number; }; -export type MockableFunction = (...args: Array) => any +export type MockableFunction = (...args: Array) => any export type MethodKeysOf = { [K in keyof T]: T[K] extends MockableFunction ? K : never }[keyof T] export type PropertyKeysOf = { [K in keyof T]: T[K] extends MockableFunction ? never : K }[keyof T] export type ArgumentsOf = T extends (...args: infer A) => any ? A : never export type ConstructorArgumentsOf = T extends new (...args: infer A) => any ? A : never -export type MaybeMockedConstructor = T extends new (...args:Array) => infer R +export type MaybeMockedConstructor = T extends new (...args:Array) => infer R ? jest.MockInstance> : T export type MockedFunction = MockWithArgs & { [K in keyof T]: T[K] } @@ -58,7 +58,7 @@ export type MaybeMocked = T extends MockableFunction ? MockedFunction : T export type Mocked = { - [P in keyof T]: T[P] extends (...args: Array) => any + [P in keyof T]: T[P] extends (...args: Array) => any ? MockInstance, jest.ArgsType> : T[P] extends jest.Constructable ? MockedClass From c0466add4dee20e5783588c42fbbc6d09644850b Mon Sep 17 00:00:00 2001 From: Rajat Date: Fri, 26 Nov 2021 21:34:50 +0530 Subject: [PATCH 5/8] adding more types --- packages/jest-mock/src/index.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/jest-mock/src/index.ts b/packages/jest-mock/src/index.ts index a0476897b5a2..dc99d3b31429 100644 --- a/packages/jest-mock/src/index.ts +++ b/packages/jest-mock/src/index.ts @@ -37,7 +37,7 @@ export type ArgumentsOf = T extends (...args: infer A) => any ? A : never export type ConstructorArgumentsOf = T extends new (...args: infer A) => any ? A : never export type MaybeMockedConstructor = T extends new (...args:Array) => infer R - ? jest.MockInstance> + ? MockInstance> : T export type MockedFunction = MockWithArgs & { [K in keyof T]: T[K] } export type MockedFunctionDeep = MockWithArgs & MockedObjectDeep @@ -56,24 +56,27 @@ export type MaybeMockedDeep = T extends MockableFunction export type MaybeMocked = T extends MockableFunction ? MockedFunction : T extends object ? MockedObject : T - +export type ArgsType = T extends (...args: infer A) => any ? A : never; export type Mocked = { [P in keyof T]: T[P] extends (...args: Array) => any - ? MockInstance, jest.ArgsType> - : T[P] extends jest.Constructable + ? MockInstance, ArgsType> + : T[P] extends Constructable ? MockedClass : T[P] } & T; - -export type MockedClass = MockInstance< +export type MockedClass = MockInstance< InstanceType, T extends new (...args: infer P) => any ? P : never > & { prototype: T extends { prototype: any } ? Mocked : never; } & T; -export interface MockWithArgs extends jest.MockInstance, ArgumentsOf> { +export interface Constructable { + new (...args: Array): any; + } + +export interface MockWithArgs extends MockInstance, ArgumentsOf> { new (...args: ConstructorArgumentsOf): T (...args: ArgumentsOf): ReturnType } From 61ce3ef367313d34ab8da7897b8912adf93620d2 Mon Sep 17 00:00:00 2001 From: Rajat Date: Fri, 26 Nov 2021 23:03:53 +0530 Subject: [PATCH 6/8] fix eslint issues --- .../jest-mock/src/__tests__/index.test.ts | 8 +- packages/jest-mock/src/index.ts | 110 +++++++++++------- 2 files changed, 69 insertions(+), 49 deletions(-) diff --git a/packages/jest-mock/src/__tests__/index.test.ts b/packages/jest-mock/src/__tests__/index.test.ts index 01dd9d067659..bcee51078f13 100644 --- a/packages/jest-mock/src/__tests__/index.test.ts +++ b/packages/jest-mock/src/__tests__/index.test.ts @@ -9,7 +9,7 @@ /* eslint-disable local/ban-types-eventually, local/prefer-rest-params-eventually */ import vm, {Context} from 'vm'; -import {ModuleMocker, fn, mocked,spyOn} from '../'; +import {ModuleMocker, fn, mocked, spyOn} from '../'; describe('moduleMocker', () => { let moduleMocker: ModuleMocker; @@ -1454,9 +1454,9 @@ describe('moduleMocker', () => { describe('mocked', () => { it('should return unmodified input', () => { - const subject = {} - expect(mocked(subject)).toBe(subject) - }) + const subject = {}; + expect(mocked(subject)).toBe(subject); + }); }); test('`fn` and `spyOn` do not throw', () => { diff --git a/packages/jest-mock/src/index.ts b/packages/jest-mock/src/index.ts index dc99d3b31429..8e25c3461f6b 100644 --- a/packages/jest-mock/src/index.ts +++ b/packages/jest-mock/src/index.ts @@ -1,4 +1,3 @@ - /** * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. * @@ -18,7 +17,11 @@ export type MockFunctionMetadataType = | 'null' | 'undefined'; -export type MockFunctionMetadata< T, Y extends Array, Type = MockFunctionMetadataType,> = { +export type MockFunctionMetadata< + T, + Y extends Array, + Type = MockFunctionMetadataType, +> = { ref?: number; members?: Record>; mockImpl?: (...args: Y) => T; @@ -29,56 +32,75 @@ export type MockFunctionMetadata< T, Y extends Array, Type = MockFunct length?: number; }; -export type MockableFunction = (...args: Array) => any -export type MethodKeysOf = { [K in keyof T]: T[K] extends MockableFunction ? K : never }[keyof T] -export type PropertyKeysOf = { [K in keyof T]: T[K] extends MockableFunction ? never : K }[keyof T] - -export type ArgumentsOf = T extends (...args: infer A) => any ? A : never - -export type ConstructorArgumentsOf = T extends new (...args: infer A) => any ? A : never -export type MaybeMockedConstructor = T extends new (...args:Array) => infer R +export type MockableFunction = (...args: Array) => any; +export type MethodKeysOf = { + [K in keyof T]: T[K] extends MockableFunction ? K : never; +}[keyof T]; +export type PropertyKeysOf = { + [K in keyof T]: T[K] extends MockableFunction ? never : K; +}[keyof T]; + +export type ArgumentsOf = T extends (...args: infer A) => any ? A : never; + +export type ConstructorArgumentsOf = T extends new (...args: infer A) => any + ? A + : never; +export type MaybeMockedConstructor = T extends new ( + ...args: Array +) => infer R ? MockInstance> - : T -export type MockedFunction = MockWithArgs & { [K in keyof T]: T[K] } -export type MockedFunctionDeep = MockWithArgs & MockedObjectDeep + : T; +export type MockedFunction = MockWithArgs & { + [K in keyof T]: T[K]; +}; +export type MockedFunctionDeep = MockWithArgs & + MockedObjectDeep; export type MockedObject = MaybeMockedConstructor & { - [K in MethodKeysOf]: T[K] extends MockableFunction ? MockedFunction : T[K] -} & { [K in PropertyKeysOf]: T[K] } + [K in MethodKeysOf]: T[K] extends MockableFunction + ? MockedFunction + : T[K]; +} & {[K in PropertyKeysOf]: T[K]}; export type MockedObjectDeep = MaybeMockedConstructor & { - [K in MethodKeysOf]: T[K] extends MockableFunction ? MockedFunctionDeep : T[K] -} & { [K in PropertyKeysOf]: MaybeMockedDeep } + [K in MethodKeysOf]: T[K] extends MockableFunction + ? MockedFunctionDeep + : T[K]; +} & {[K in PropertyKeysOf]: MaybeMockedDeep}; export type MaybeMockedDeep = T extends MockableFunction ? MockedFunctionDeep - : T extends object + : T extends object ? MockedObjectDeep - : T + : T; -export type MaybeMocked = T extends MockableFunction ? MockedFunction : T extends object ? MockedObject : T +export type MaybeMocked = T extends MockableFunction + ? MockedFunction + : T extends object + ? MockedObject + : T; export type ArgsType = T extends (...args: infer A) => any ? A : never; export type Mocked = { [P in keyof T]: T[P] extends (...args: Array) => any - ? MockInstance, ArgsType> - : T[P] extends Constructable - ? MockedClass - : T[P] -} & - T; + ? MockInstance, ArgsType> + : T[P] extends Constructable + ? MockedClass + : T[P]; +} & T; export type MockedClass = MockInstance< - InstanceType, - T extends new (...args: infer P) => any ? P : never - > & { - prototype: T extends { prototype: any } ? Mocked : never; - } & T; + InstanceType, + T extends new (...args: infer P) => any ? P : never +> & { + prototype: T extends {prototype: any} ? Mocked : never; +} & T; export interface Constructable { - new (...args: Array): any; - } + new (...args: Array): any; +} -export interface MockWithArgs extends MockInstance, ArgumentsOf> { - new (...args: ConstructorArgumentsOf): T - (...args: ArgumentsOf): ReturnType +export interface MockWithArgs + extends MockInstance, ArgumentsOf> { + new (...args: ConstructorArgumentsOf): T; + (...args: ArgumentsOf): ReturnType; } export interface Mock = Array> @@ -1158,21 +1180,19 @@ export class ModuleMocker { private _typeOf(value: any): string { return value == null ? '' + value : typeof value; } - - // the typings test helper - mocked(item: T, deep?: false): MaybeMocked - - mocked(item: T, deep: true): MaybeMockedDeep - mocked(item: T, _deep = false): MaybeMocked | MaybeMockedDeep { + // the typings test helper + mocked(item: T, deep?: false): MaybeMocked; - return item as any + mocked(item: T, deep: true): MaybeMockedDeep; -} + mocked(item: T, _deep = false): MaybeMocked | MaybeMockedDeep { + return item as any; + } } const JestMock = new ModuleMocker(global as unknown as typeof globalThis); export const fn = JestMock.fn.bind(JestMock); export const spyOn = JestMock.spyOn.bind(JestMock); -export const mocked= JestMock.mocked.bind(JestMock); +export const mocked = JestMock.mocked.bind(JestMock); From 0c8014f94a1f75f00a3b05a66f8f089f22eecee9 Mon Sep 17 00:00:00 2001 From: Rajat Date: Sun, 28 Nov 2021 01:34:14 +0530 Subject: [PATCH 7/8] Update docs --- docs/JestObjectAPI.md | 45 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/docs/JestObjectAPI.md b/docs/JestObjectAPI.md index ddfd7fafea74..b724d82c51b1 100644 --- a/docs/JestObjectAPI.md +++ b/docs/JestObjectAPI.md @@ -578,6 +578,51 @@ Returns the `jest` object for chaining. 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. +### `jest.mocked(item: T, deep = false)` + +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`). + +_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._ + +Example: + +```ts +// foo.ts +export const foo = { + a: { + b: { + c: { + hello: (name: string) => `Hello, ${name}`, + }, + }, + }, + name: () => 'foo', +} +``` + +```ts +// foo.spec.ts +import { foo } from './foo' +jest.mock('./foo') + +// here the whole foo var is mocked deeply +const mockedFoo = jest.mocked(foo, true) + +test('deep', () => { + // there will be no TS error here, and you'll have completion in modern IDEs + mockedFoo.a.b.c.hello('me') + // same here + expect(mockedFoo.a.b.c.hello.mock.calls).toHaveLength(1) +}) + +test('direct', () => { + foo.name() + // here only foo.name is mocked (or its methods if it's an object) + expect(mocked(foo.name).mock.calls).toHaveLength(1) +}) +``` + + ## Mock Timers ### `jest.useFakeTimers(implementation?: 'modern' | 'legacy')` From 93234bd8c1de61fa1a99bf2aeceb9c4ca40c67ac Mon Sep 17 00:00:00 2001 From: Rajat Date: Sun, 28 Nov 2021 12:00:42 +0530 Subject: [PATCH 8/8] prettier --- CHANGELOG.md | 2 +- docs/JestObjectAPI.md | 21 ++++++++++----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c640ada15d9a..0d563fab9e04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - `[jest-core]` Add support for `testResultsProcessor` written in ESM ([#12006](https://github.com/facebook/jest/pull/12006)) - `[jest-diff, pretty-format]` Add `compareKeys` option for custom sorting of object keys ([#11992](https://github.com/facebook/jest/pull/11992)) -- `[jest-mock]` Add `ts-jest` mock util functions ([#12089](https://github.com/facebook/jest/pull/12089)) +- `[jest-mock]` Add `ts-jest` mock util functions ([#12089](https://github.com/facebook/jest/pull/12089)) ### Fixes diff --git a/docs/JestObjectAPI.md b/docs/JestObjectAPI.md index b724d82c51b1..c4cd184b5661 100644 --- a/docs/JestObjectAPI.md +++ b/docs/JestObjectAPI.md @@ -597,32 +597,31 @@ export const foo = { }, }, name: () => 'foo', -} +}; ``` ```ts // foo.spec.ts -import { foo } from './foo' -jest.mock('./foo') +import {foo} from './foo'; +jest.mock('./foo'); // here the whole foo var is mocked deeply -const mockedFoo = jest.mocked(foo, true) +const mockedFoo = jest.mocked(foo, true); test('deep', () => { // there will be no TS error here, and you'll have completion in modern IDEs - mockedFoo.a.b.c.hello('me') + mockedFoo.a.b.c.hello('me'); // same here - expect(mockedFoo.a.b.c.hello.mock.calls).toHaveLength(1) -}) + expect(mockedFoo.a.b.c.hello.mock.calls).toHaveLength(1); +}); test('direct', () => { - foo.name() + foo.name(); // here only foo.name is mocked (or its methods if it's an object) - expect(mocked(foo.name).mock.calls).toHaveLength(1) -}) + expect(mocked(foo.name).mock.calls).toHaveLength(1); +}); ``` - ## Mock Timers ### `jest.useFakeTimers(implementation?: 'modern' | 'legacy')`