Skip to content

Commit

Permalink
feat(jest-mock): Export Mock, MockInstance, SpyInstance types (#10138)
Browse files Browse the repository at this point in the history
  • Loading branch information
anilanar committed Jun 23, 2020
1 parent 504cace commit 95b94e8
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 52 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,7 @@

### Features

- `[jest-mock]` Export `Mock`, `MockInstance`, `SpyInstance` types ([#10138](https://github.com/facebook/jest/pull/10138))
- `[jest-config]` Support config files exporting (`async`) `function`s ([#10001](https://github.com/facebook/jest/pull/10001))
- `[jest-cli, jest-core]` Add `--selectProjects` CLI argument to filter test suites by project name ([#8612](https://github.com/facebook/jest/pull/8612))
- `[jest-cli, jest-init]` Add `coverageProvider` to `jest --init` prompts ([#10044](https://github.com/facebook/jest/pull/10044))
Expand Down
108 changes: 56 additions & 52 deletions packages/jest-mock/src/index.ts
Expand Up @@ -33,6 +33,39 @@ namespace JestMock {
value?: T;
length?: number;
};

export interface Mock<T, Y extends Array<unknown> = Array<unknown>>
extends Function,
MockInstance<T, Y> {
new (...args: Y): T;
(...args: Y): T;
}

export interface SpyInstance<T, Y extends Array<unknown>>
extends MockInstance<T, Y> {}

export interface MockInstance<T, Y extends Array<unknown>> {
_isMockFunction: true;
_protoImpl: Function;
getMockName(): string;
getMockImplementation(): Function | undefined;
mock: MockFunctionState<T, Y>;
mockClear(): this;
mockReset(): this;
mockRestore(): void;
mockImplementation(fn: (...args: Y) => T): this;
mockImplementation(fn: () => Promise<T>): this;
mockImplementationOnce(fn: (...args: Y) => T): this;
mockImplementationOnce(fn: () => Promise<T>): this;
mockName(name: string): this;
mockReturnThis(): this;
mockReturnValue(value: T): this;
mockReturnValueOnce(value: T): this;
mockResolvedValue(value: T): this;
mockResolvedValueOnce(value: T): this;
mockRejectedValue(value: T): this;
mockRejectedValueOnce(value: T): this;
}
}

/**
Expand Down Expand Up @@ -87,38 +120,6 @@ type FunctionPropertyNames<T> = {
}[keyof T] &
string;

interface Mock<T, Y extends Array<unknown> = Array<unknown>>
extends Function,
MockInstance<T, Y> {
new (...args: Y): T;
(...args: Y): T;
}

interface SpyInstance<T, Y extends Array<unknown>> extends MockInstance<T, Y> {}

interface MockInstance<T, Y extends Array<unknown>> {
_isMockFunction: true;
_protoImpl: Function;
getMockName(): string;
getMockImplementation(): Function | undefined;
mock: MockFunctionState<T, Y>;
mockClear(): this;
mockReset(): this;
mockRestore(): void;
mockImplementation(fn: (...args: Y) => T): this;
mockImplementation(fn: () => Promise<T>): this;
mockImplementationOnce(fn: (...args: Y) => T): this;
mockImplementationOnce(fn: () => Promise<T>): this;
mockName(name: string): this;
mockReturnThis(): this;
mockReturnValue(value: T): this;
mockReturnValueOnce(value: T): this;
mockResolvedValue(value: T): this;
mockResolvedValueOnce(value: T): this;
mockRejectedValue(value: T): this;
mockRejectedValueOnce(value: T): this;
}

const MOCK_CONSTRUCTOR_NAME = 'mockConstructor';

const FUNCTION_NAME_RESERVED_PATTERN = /[\s!-\/:-@\[-`{-~]/;
Expand Down Expand Up @@ -363,7 +364,10 @@ function isReadonlyProp(object: any, prop: string): boolean {

class ModuleMockerClass {
private _environmentGlobal: Global;
private _mockState: WeakMap<Mock<any, any>, MockFunctionState<any, any>>;
private _mockState: WeakMap<
JestMock.Mock<any, any>,
MockFunctionState<any, any>
>;
private _mockConfigRegistry: WeakMap<Function, MockFunctionConfig>;
private _spyState: Set<() => void>;
private _invocationCallCounter: number;
Expand Down Expand Up @@ -430,7 +434,7 @@ class ModuleMockerClass {
}

private _ensureMockConfig<T, Y extends Array<unknown>>(
f: Mock<T, Y>,
f: JestMock.Mock<T, Y>,
): MockFunctionConfig {
let config = this._mockConfigRegistry.get(f);
if (!config) {
Expand All @@ -441,7 +445,7 @@ class ModuleMockerClass {
}

private _ensureMockState<T, Y extends Array<unknown>>(
f: Mock<T, Y>,
f: JestMock.Mock<T, Y>,
): MockFunctionState<T, Y> {
let state = this._mockState.get(f);
if (!state) {
Expand Down Expand Up @@ -495,7 +499,7 @@ class ModuleMockerClass {
private _makeComponent<T, Y extends Array<unknown>>(
metadata: JestMock.MockFunctionMetadata<T, Y, 'function'>,
restore?: () => void,
): Mock<T, Y>;
): JestMock.Mock<T, Y>;
private _makeComponent<T, Y extends Array<unknown>>(
metadata: JestMock.MockFunctionMetadata<T, Y>,
restore?: () => void,
Expand All @@ -505,7 +509,7 @@ class ModuleMockerClass {
| RegExp
| T
| undefined
| Mock<T, Y> {
| JestMock.Mock<T, Y> {
if (metadata.type === 'object') {
return new this._environmentGlobal.Object();
} else if (metadata.type === 'array') {
Expand Down Expand Up @@ -617,7 +621,7 @@ class ModuleMockerClass {
const f = (this._createMockFunction(
metadata,
mockConstructor,
) as unknown) as Mock<T, Y>;
) as unknown) as JestMock.Mock<T, Y>;
f._isMockFunction = true;
f.getMockImplementation = () => this._ensureMockConfig(f).mockImpl;

Expand Down Expand Up @@ -673,7 +677,7 @@ class ModuleMockerClass {

f.mockImplementationOnce = (
fn: ((...args: Y) => T) | (() => Promise<T>),
): Mock<T, Y> => {
): JestMock.Mock<T, Y> => {
// next function call will use this mock implementation return value
// or default mock implementation return value
const mockConfig = this._ensureMockConfig(f);
Expand All @@ -683,7 +687,7 @@ class ModuleMockerClass {

f.mockImplementation = (
fn: ((...args: Y) => T) | (() => Promise<T>),
): Mock<T, Y> => {
): JestMock.Mock<T, Y> => {
// next function call will use mock implementation return value
const mockConfig = this._ensureMockConfig(f);
mockConfig.mockImpl = fn;
Expand Down Expand Up @@ -789,9 +793,9 @@ class ModuleMockerClass {
| RegExp
| T
| undefined
| Mock<T, Y>;
| JestMock.Mock<T, Y>;
},
): Mock<T, Y> {
): JestMock.Mock<T, Y> {
// metadata not compatible but it's the same type, maybe problem with
// overloading of _makeComponent and not _generateMock?
// @ts-expect-error
Expand Down Expand Up @@ -822,7 +826,7 @@ class ModuleMockerClass {
mock.prototype.constructor = mock;
}

return mock as Mock<T, Y>;
return mock as JestMock.Mock<T, Y>;
}

/**
Expand All @@ -832,7 +836,7 @@ class ModuleMockerClass {
*/
generateFromMetadata<T, Y extends Array<unknown>>(
_metadata: JestMock.MockFunctionMetadata<T, Y>,
): Mock<T, Y> {
): JestMock.Mock<T, Y> {
const callbacks: Array<Function> = [];
const refs = {};
const mock = this._generateMock(_metadata, callbacks, refs);
Expand Down Expand Up @@ -913,13 +917,13 @@ class ModuleMockerClass {
return metadata;
}

isMockFunction<T>(fn: any): fn is Mock<T> {
isMockFunction<T>(fn: any): fn is JestMock.Mock<T> {
return !!fn && fn._isMockFunction === true;
}

fn<T, Y extends Array<unknown>>(
implementation?: (...args: Y) => T,
): Mock<T, Y> {
): JestMock.Mock<T, Y> {
const length = implementation ? implementation.length : 0;
const fn = this._makeComponent<T, Y>({length, type: 'function'});
if (implementation) {
Expand All @@ -932,19 +936,19 @@ class ModuleMockerClass {
object: T,
methodName: M,
accessType: 'get',
): SpyInstance<T[M], []>;
): JestMock.SpyInstance<T[M], []>;

spyOn<T extends {}, M extends NonFunctionPropertyNames<T>>(
object: T,
methodName: M,
accessType: 'set',
): SpyInstance<void, [T[M]]>;
): JestMock.SpyInstance<void, [T[M]]>;

spyOn<T extends {}, M extends FunctionPropertyNames<T>>(
object: T,
methodName: M,
): T[M] extends (...args: Array<any>) => any
? SpyInstance<ReturnType<T[M]>, Parameters<T[M]>>
? JestMock.SpyInstance<ReturnType<T[M]>, Parameters<T[M]>>
: never;

spyOn<T extends {}, M extends NonFunctionPropertyNames<T>>(
Expand Down Expand Up @@ -999,7 +1003,7 @@ class ModuleMockerClass {
obj: T,
propertyName: M,
accessType: 'get' | 'set' = 'get',
): Mock<T> {
): JestMock.Mock<T> {
if (typeof obj !== 'object' && typeof obj !== 'function') {
throw new Error(
'Cannot spyOn on a primitive value; ' + this._typeOf(obj) + ' given',
Expand Down Expand Up @@ -1058,7 +1062,7 @@ class ModuleMockerClass {
Object.defineProperty(obj, propertyName, descriptor!);
});

(descriptor[accessType] as Mock<T>).mockImplementation(function (
(descriptor[accessType] as JestMock.Mock<T>).mockImplementation(function (
this: unknown,
) {
// @ts-expect-error
Expand All @@ -1067,7 +1071,7 @@ class ModuleMockerClass {
}

Object.defineProperty(obj, propertyName, descriptor);
return descriptor[accessType] as Mock<T>;
return descriptor[accessType] as JestMock.Mock<T>;
}

clearAllMocks() {
Expand Down

0 comments on commit 95b94e8

Please sign in to comment.