Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(jest-mock): Export Mock, MockInstance, SpyInstance types #10138

Merged
merged 1 commit into from Jun 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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