Skip to content

Commit

Permalink
feat: add jest.Mocked
Browse files Browse the repository at this point in the history
  • Loading branch information
mrazauskas committed Apr 24, 2022
1 parent 62afb83 commit 58dc610
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 21 deletions.
3 changes: 2 additions & 1 deletion packages/jest-globals/package.json
Expand Up @@ -22,7 +22,8 @@
"dependencies": {
"@jest/environment": "^28.0.0-alpha.11",
"@jest/expect": "^28.0.0-alpha.11",
"@jest/types": "^28.0.0-alpha.9"
"@jest/types": "^28.0.0-alpha.9",
"jest-mock": "^28.0.0-alpha.9"
},
"publishConfig": {
"access": "public"
Expand Down
20 changes: 19 additions & 1 deletion packages/jest-globals/src/index.ts
Expand Up @@ -9,7 +9,7 @@ import type {Jest} from '@jest/environment';
import type {JestExpect} from '@jest/expect';
import type {Global} from '@jest/types';

export declare const jest: Jest;
declare const jest: Jest;

export declare const expect: JestExpect;

Expand All @@ -26,6 +26,24 @@ export declare const beforeEach: Global.GlobalAdditions['beforeEach'];
export declare const afterEach: Global.GlobalAdditions['afterEach'];
export declare const afterAll: Global.GlobalAdditions['afterAll'];

declare namespace jest {
/**
* Wraps class, function, object or module with mock definitions.
*
* @example
*
* jest.mock('../api');
* import * as api from '../api';
*
* const mockApi = api as jest.Mocked<typeof api>;
*
* mockApi.someMethod.mockImplementation(() => 'test');
*/
type Mocked<T> = import('jest-mock').Mocked<T>;
}

export {jest};

throw new Error(
'Do not import `@jest/globals` outside of the Jest test environment',
);
1 change: 1 addition & 0 deletions packages/jest-globals/tsconfig.json
Expand Up @@ -11,6 +11,7 @@
"references": [
{"path": "../jest-environment"},
{"path": "../jest-expect"},
{"path": "../jest-mock"},
{"path": "../jest-types"}
]
}
86 changes: 86 additions & 0 deletions packages/jest-mock/__typetests__/Mocked.test.ts
@@ -0,0 +1,86 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import console = require('console');
import {expectAssignable, expectType} from 'tsd-lite';
import type {MockInstance, Mocked} from 'jest-mock';

/// mocks class

class ExampleClass {
constructor(c: string, d?: boolean) {}

// _propertyB: false;

methodA() {
return true;
}
methodB(a: string, b: number) {
return;
}
methodC(e: any) {
throw new Error();
}

// propertyA: 'abc',
}

const MockExampleClass = ExampleClass as Mocked<typeof ExampleClass>;

const xx = MockExampleClass.mock.calls[0];
const yy = MockExampleClass.prototype.methodB.mock.calls[0];

const ww = MockExampleClass.mock.instances[0].methodB.mock.calls[0];

const mockExample = new MockExampleClass('c') as Mocked<
InstanceType<typeof MockExampleClass>
>;

const zz = mockExample.methodB.mock.calls[0];

/// mocks function

function someFunction(a: number, b?: string): boolean {
return true;
}

async function someAsyncFunction(a: Array<boolean>): Promise<string> {
return 'true';
}

const mockFunction = someFunction as Mocked<typeof someFunction>;

expectType<number>(mockFunction.mock.calls[0][0]);
expectType<string | undefined>(mockFunction.mock.calls[0][1]);

const mockFunctionResult = mockFunction.mock.results[0];

if (mockFunctionResult.type === 'return') {
expectType<boolean>(mockFunctionResult.value);
}

const mockAsyncFunction = someAsyncFunction as Mocked<typeof someAsyncFunction>;

expectType<Array<boolean>>(mockAsyncFunction.mock.calls[0][0]);
// expectError(mockAsyncFunction.mock.calls[0][1]);

const mockAsyncFunctionResult = mockAsyncFunction.mock.results[0];

if (mockAsyncFunctionResult.type === 'return') {
expectType<Promise<string>>(mockAsyncFunctionResult.value);
}

// mocks object

const mockConsole = console as Mocked<typeof console>;

expectAssignable<typeof console.log>(
mockConsole.log.mockImplementation(() => {}),
);
expectAssignable<MockInstance<typeof console.log>>(
mockConsole.log.mockImplementation(() => {}),
);
2 changes: 1 addition & 1 deletion packages/jest-mock/__typetests__/tsconfig.json
Expand Up @@ -6,7 +6,7 @@
"noUnusedParameters": false,
"skipLibCheck": true,

"types": []
"types": ["node"]
},
"include": ["./**/*"]
}
16 changes: 2 additions & 14 deletions packages/jest-mock/src/index.ts
Expand Up @@ -7,6 +7,8 @@

/* eslint-disable local/ban-types-eventually, local/prefer-rest-params-eventually */

export type {Mocked} from './types';

export type MockFunctionMetadataType =
| 'object'
| 'array'
Expand Down Expand Up @@ -98,20 +100,6 @@ export type MaybeMockedDeep<T> = T extends FunctionLike
? MockedObjectDeep<T>
: T;

export type Mocked<T> = {
[P in keyof T]: T[P] extends FunctionLike
? MockInstance<T[P]>
: T[P] extends ClassLike
? MockedClass<T[P]>
: T[P];
} & T;

export type MockedClass<T extends ClassLike> = MockInstance<
(args: T extends new (...args: infer P) => any ? P : never) => InstanceType<T>
> & {
prototype: T extends {prototype: any} ? Mocked<T['prototype']> : never;
} & T;

export type UnknownFunction = (...args: Array<unknown>) => unknown;

/**
Expand Down
37 changes: 37 additions & 0 deletions packages/jest-mock/src/types.ts
@@ -0,0 +1,37 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import type {MockInstance} from './index';

type ClassLike = {new (...args: any): any};
type FunctionLike = (...args: any) => any;

type MockedClass<T extends ClassLike> = MockInstance<
(...args: ConstructorParameters<T>) => Mocked<InstanceType<T>>
> & {
prototype: T extends {prototype: any} ? Mocked<T['prototype']> : never;
} & MockedObject<T>;

type MockedFunction<T extends FunctionLike> = MockInstance<T> & MockedObject<T>;

type MockedObject<T> = {
[K in keyof T]: T[K] extends ClassLike
? MockedClass<T[K]>
: T[K] extends FunctionLike
? MockedFunction<T[K]>
: T[K] extends object
? MockedObject<T[K]>
: T[K];
} & T;

export type Mocked<T> = T extends ClassLike
? MockedClass<T>
: T extends FunctionLike
? MockedFunction<T>
: T extends object
? MockedObject<T>
: T;
15 changes: 11 additions & 4 deletions packages/jest-types/__typetests__/jest.test.ts
Expand Up @@ -7,7 +7,7 @@

import {expectError, expectType} from 'tsd-lite';
import {jest} from '@jest/globals';
import type {Mock, ModuleMocker, SpyInstance} from 'jest-mock';
import type {Mock, Mocked, ModuleMocker, SpyInstance} from 'jest-mock';

expectType<typeof jest>(
jest
Expand Down Expand Up @@ -117,7 +117,6 @@ expectError(jest.unmock());

// Mock Functions

expectType<typeof jest>(jest.retryTimes(3, {logErrorsBeforeRetry: true}));
expectType<typeof jest>(jest.clearAllMocks());
expectError(jest.clearAllMocks('moduleName'));

Expand Down Expand Up @@ -301,8 +300,16 @@ expectError(jest.useRealTimers(true));

// Misc

expectType<typeof jest>(jest.setTimeout(6000));
expectError(jest.setTimeout());
type VoidFn = () => void;
declare const voidFn: VoidFn;

expectType<Mocked<VoidFn>>(voidFn as jest.Mocked<VoidFn>);

expectType<typeof jest>(jest.retryTimes(3));
expectType<typeof jest>(jest.retryTimes(3, {logErrorsBeforeRetry: true}));
expectError(jest.retryTimes(3, {logErrorsBeforeRetry: 'all'}));
expectError(jest.retryTimes({logErrorsBeforeRetry: true}));
expectError(jest.retryTimes());

expectType<typeof jest>(jest.setTimeout(6000));
expectError(jest.setTimeout());
1 change: 1 addition & 0 deletions yarn.lock
Expand Up @@ -2658,6 +2658,7 @@ __metadata:
"@jest/environment": ^28.0.0-alpha.11
"@jest/expect": ^28.0.0-alpha.11
"@jest/types": ^28.0.0-alpha.9
jest-mock: ^28.0.0-alpha.9
languageName: unknown
linkType: soft

Expand Down

0 comments on commit 58dc610

Please sign in to comment.