Skip to content

Commit

Permalink
add mock type utils to jest
Browse files Browse the repository at this point in the history
  • Loading branch information
mrazauskas committed Feb 18, 2022
1 parent 7d4595e commit 5f610f9
Show file tree
Hide file tree
Showing 10 changed files with 149 additions and 46 deletions.
File renamed without changes.
81 changes: 81 additions & 0 deletions packages/jest-globals/index.d.ts
@@ -0,0 +1,81 @@
/**
* 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 {Jest} from '@jest/environment';
import type {JestExpect} from '@jest/expect';
import type {Global} from '@jest/types';

export declare const jest: Jest;

export declare const expect: JestExpect;

export declare const test: Global.GlobalAdditions['test'];
export declare const xtest: Global.GlobalAdditions['xtest'];

export declare const it: Global.GlobalAdditions['it'];
export declare const fit: Global.GlobalAdditions['fit'];
export declare const xit: Global.GlobalAdditions['xit'];

export declare const describe: Global.GlobalAdditions['describe'];
export declare const fdescribe: Global.GlobalAdditions['fdescribe'];
export declare const xdescribe: Global.GlobalAdditions['xdescribe'];

export declare const beforeAll: Global.GlobalAdditions['beforeAll'];
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 an object or a module with mock definitions.
*
* @example
*
* jest.mock('../api');
* import * as api from '../api';
*
* const mockApi = api as jest.Mocked<typeof api>;
*
* api.MyApi.prototype.myApiMethod.mockImplementation(() => 'test');
*/
export type Mocked<T> = import('jest-mock').Mocked<T>;

/**
* Wraps a class with mock definitions.
*
* @example
*
* import {MyClass} from './library';
* jest.mock('./library');
*
* const mockMyClass = MyClass as jest.MockedClass<typeof MyClass>;
*
* expect(mockMyClass.mock.calls[0][0]).toBe(42); // Constructor calls
* expect(mockMyClass.prototype.myMethod.mock.calls[0][0]).toBe(42); // Method calls
*/
export type MockedClass<T> = import('jest-mock').MockedClass<T>;

/**
* Wraps a function with mock definitions.
*
* @example
*
* import {myFunction} from './library';
* jest.mock('./library');
*
* const mockMyFunction = myFunction as jest.MockedFunction<typeof myFunction>;
*
* expect(mockMyFunction.mock.calls[0][0]).toBe(42);
*/
export type MockedFunction<T> = import('jest-mock').MockedFunction<T>;

/**
* Wraps a function with spy definitions.
*/
export type SpiedFunction<T> = import('jest-mock').SpiedFunction<T>;
}
11 changes: 11 additions & 0 deletions packages/jest-globals/index.js
@@ -0,0 +1,11 @@
'use strict';

/**
* 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.
*/
throw new Error(
'Do not import `@jest/globals` outside of the Jest test environment'
);
11 changes: 6 additions & 5 deletions packages/jest-globals/package.json
Expand Up @@ -10,19 +10,20 @@
"node": "^12.13.0 || ^14.15.0 || ^16.13.0 || >=17.0.0"
},
"license": "MIT",
"main": "./build/index.js",
"types": "./build/index.d.ts",
"main": "./index.js",
"types": "./index.d.ts",
"exports": {
".": {
"types": "./build/index.d.ts",
"default": "./build/index.js"
"types": "./index.d.ts",
"default": "./index.js"
},
"./package.json": "./package.json"
},
"dependencies": {
"@jest/environment": "^28.0.0-alpha.3",
"@jest/expect": "^28.0.0-alpha.3",
"@jest/types": "^28.0.0-alpha.3"
"@jest/types": "^28.0.0-alpha.3",
"jest-mock": "^28.0.0-alpha.3"
},
"publishConfig": {
"access": "public"
Expand Down
31 changes: 0 additions & 31 deletions packages/jest-globals/src/index.ts

This file was deleted.

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"}
]
}
26 changes: 18 additions & 8 deletions packages/jest-mock/src/index.ts
Expand Up @@ -78,23 +78,33 @@ export type MaybeMocked<T> = T extends MockableFunction
? MockedObject<T>
: T;

export type ArgsType<T> = T extends (...args: infer A) => any ? A : never;
type ClassLike = {
new (...args: Array<unknown>): unknown;
};

type FunctionLike = {
(...args: Array<unknown>): unknown;
};

export type Mocked<T> = {
[P in keyof T]: T[P] extends (...args: Array<any>) => any
? MockInstance<ReturnType<T[P]>, ArgsType<T[P]>>
: T[P] extends Constructable
[P in keyof T]: T[P] extends FunctionLike
? MockInstance<ReturnType<T[P]>, Parameters<T[P]>>
: T[P] extends ClassLike
? MockedClass<T[P]>
: T[P];
} & T;
export type MockedClass<T extends Constructable> = MockInstance<

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

export type SpiedFunction<T extends FunctionLike> = SpyInstance<
ReturnType<T>,
Parameters<T>
>;

export interface MockWithArgs<T extends MockableFunction>
extends MockInstance<ReturnType<T>, ArgumentsOf<T>> {
Expand Down
29 changes: 29 additions & 0 deletions packages/jest-types/__typetests__/mocks-and-spies.test.ts
@@ -0,0 +1,29 @@
/**
* 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 {expectType} from 'tsd-lite';
import {jest} from '@jest/globals';
import type {Mock} from 'jest-mock';

// Mock

const mock1: Mock<number> = jest.fn();

expectType<Mock<number>>(mock1);

// MockedFunction

function testFunction(num: number, str: string): boolean {
return true;
}

const mockedTestFunction = testFunction as jest.MockedFunction<
typeof testFunction
>;

expectType<number>(mockedTestFunction.mock.calls[0][0]);
expectType<string>(mockedTestFunction.mock.calls[0][1]);
4 changes: 2 additions & 2 deletions scripts/buildUtils.js
Expand Up @@ -74,12 +74,12 @@ module.exports.getPackages = function getPackages() {
if (pkg.types) {
assert.strictEqual(
pkg.main,
'./build/index.js',
pkg.name === '@jest/globals' ? './index.js' : './build/index.js',
`Package "${pkg.name}" should have "./build/index.js" as main`,
);
assert.strictEqual(
pkg.types,
'./build/index.d.ts',
pkg.name === '@jest/globals' ? './index.d.ts' : './build/index.d.ts',
`Package "${pkg.name}" should have "./build/index.d.ts" as types`,
);
} else {
Expand Down
1 change: 1 addition & 0 deletions yarn.lock
Expand Up @@ -2621,6 +2621,7 @@ __metadata:
"@jest/environment": ^28.0.0-alpha.3
"@jest/expect": ^28.0.0-alpha.3
"@jest/types": ^28.0.0-alpha.3
jest-mock: ^28.0.0-alpha.3
languageName: unknown
linkType: soft

Expand Down

0 comments on commit 5f610f9

Please sign in to comment.