Skip to content

Commit

Permalink
Replace usage of globals with @jest/globals package
Browse files Browse the repository at this point in the history
  • Loading branch information
macko911 committed May 19, 2023
1 parent 1860950 commit da688eb
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 38 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
"ts-essentials": "^7.0.3"
},
"devDependencies": {
"@types/jest": "^27.5.0",
"coveralls": "^3.1.1",
"jest": "^29.5.0",
"prettier": "^2.3.2",
Expand All @@ -32,6 +31,7 @@
"typescript": "^5.0.2"
},
"peerDependencies": {
"@jest/globals": "^28.0.0 || ^29.0.0",
"jest": "^24.0.0 || ^25.0.0 || ^26.0.0 || ^27.0.0 || ^28.0.0 || ^29.0.0",
"typescript": "^3.0.0 || ^4.0.0 || ^5.0.0"
},
Expand Down
34 changes: 18 additions & 16 deletions src/CalledWithFn.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { CalledWithMock } from './Mock';
import { Matcher, MatchersOrLiterals } from './Matchers';
import { jest } from '@jest/globals';
import type { FunctionLike } from 'jest-mock';

interface CalledWithStackItem<T, Y extends any[]> {
args: MatchersOrLiterals<Y>;
calledWithFn: jest.Mock<T, Y>;
interface CalledWithStackItem<T extends FunctionLike> {
args: MatchersOrLiterals<[...Parameters<T>]>;
calledWithFn: jest.Mock<T>;
}

interface JestAsymmetricMatcher {
Expand All @@ -13,11 +15,11 @@ function isJestAsymmetricMatcher(obj: any): obj is JestAsymmetricMatcher {
return !!obj && typeof obj === 'object' && 'asymmetricMatch' in obj && typeof obj.asymmetricMatch === 'function';
}

const checkCalledWith = <T, Y extends any[]>(
calledWithStack: CalledWithStackItem<T, Y>[],
actualArgs: Y,
fallbackMockImplementation?: (...args: Y) => T
): T => {
const checkCalledWith = <T extends FunctionLike>(
calledWithStack: CalledWithStackItem<T>[],
actualArgs: [...Parameters<T>],
fallbackMockImplementation?: T
): ReturnType<T> => {
const calledWithInstance = calledWithStack.find((instance) =>
instance.args.every((matcher, i) => {
if (matcher instanceof Matcher) {
Expand All @@ -32,34 +34,34 @@ const checkCalledWith = <T, Y extends any[]>(
})
);

// @ts-ignore cannot return undefined, but this will fail the test if there is an expectation which is what we want
return calledWithInstance
? calledWithInstance.calledWithFn(...actualArgs)
: fallbackMockImplementation && fallbackMockImplementation(...actualArgs);
};

export const calledWithFn = <T, Y extends any[]>({
export const calledWithFn = <T extends FunctionLike>({
fallbackMockImplementation,
}: { fallbackMockImplementation?: (...args: Y) => T } = {}): CalledWithMock<T, Y> => {
const fn: jest.Mock<T, Y> = jest.fn(fallbackMockImplementation);
let calledWithStack: CalledWithStackItem<T, Y>[] = [];
}: { fallbackMockImplementation?: T } = {}): CalledWithMock<T> => {
const fn = jest.fn(fallbackMockImplementation);
let calledWithStack: CalledWithStackItem<T>[] = [];

(fn as CalledWithMock<T, Y>).calledWith = (...args) => {
(fn as CalledWithMock<T>).calledWith = (...args) => {
// We create new function to delegate any interactions (mockReturnValue etc.) to for this set of args.
// If that set of args is matched, we just call that jest.fn() for the result.
const calledWithFn = jest.fn(fallbackMockImplementation);
const mockImplementation = fn.getMockImplementation();
if (!mockImplementation || mockImplementation === fallbackMockImplementation) {
// Our original function gets a mock implementation which handles the matching
fn.mockImplementation((...args: Y) => checkCalledWith(calledWithStack, args, fallbackMockImplementation));
// @ts-expect-error '(...args: any) => ReturnType<T>' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'FunctionLike'.
fn.mockImplementation((...args) => checkCalledWith(calledWithStack, args, fallbackMockImplementation));
calledWithStack = [];
}
calledWithStack.unshift({ args, calledWithFn });

return calledWithFn;
};

return fn as CalledWithMock<T, Y>;
return fn as CalledWithMock<T>;
};

export default calledWithFn;
1 change: 1 addition & 0 deletions src/Matchers.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { beforeEach, describe, expect, jest, test } from '@jest/globals';
import {
any,
anyArray,
Expand Down
12 changes: 10 additions & 2 deletions src/Mock.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import mock, { mockClear, mockDeep, mockReset, mockFn, JestMockExtended } from '
import { anyNumber } from './Matchers';
import calledWithFn from './CalledWithFn';
import { MockProxy } from './Mock';
import { describe, expect, jest, test } from '@jest/globals';
import { fail } from 'assert';

interface MockInt {
id: number;
Expand Down Expand Up @@ -248,14 +250,20 @@ describe('jest-mock-extended', () => {

test('Support jest matcher', () => {
const mockObj = mock<MockInt>();
mockObj.getSomethingWithArgs.calledWith(expect.anything(), expect.anything()).mockReturnValue(3);
mockObj.getSomethingWithArgs
// @ts-expect-error Type 'AsymmetricMatcher_2' is missing the following properties from type 'Matcher<number>': $$typeof, description
.calledWith(expect.anything(), expect.anything())
.mockReturnValue(3);

expect(mockObj.getSomethingWithArgs(1, 2)).toBe(3);
});

test('Suport mix Matchers with literals and with jest matcher', () => {
const mockObj = mock<MockInt>();
mockObj.getSomethingWithMoreArgs.calledWith(anyNumber(), expect.anything(), 3).mockReturnValue(4);
mockObj.getSomethingWithMoreArgs
// @ts-expect-error Type 'AsymmetricMatcher_2' is not assignable to type 'number | Matcher<number>'
.calledWith(anyNumber(), expect.anything(), 3)
.mockReturnValue(4);

expect(mockObj.getSomethingWithMoreArgs(1, 2, 3)).toBe(4);
expect(mockObj.getSomethingWithMoreArgs(1, 2, 4)).toBeUndefined;
Expand Down
27 changes: 11 additions & 16 deletions src/Mock.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import calledWithFn from './CalledWithFn';
import { MatchersOrLiterals } from './Matchers';
import { DeepPartial } from 'ts-essentials';
import { jest } from '@jest/globals';
import type { FunctionLike } from 'jest-mock';

type ProxiedProperty = string | number | symbol;

Expand All @@ -26,26 +28,23 @@ export const JestMockExtended = {
},
};

export interface CalledWithMock<T, Y extends any[]> extends jest.Mock<T, Y> {
calledWith: (...args: Y | MatchersOrLiterals<Y>) => jest.Mock<T, Y>;
export interface CalledWithMock<T extends FunctionLike> extends jest.Mock<T> {
calledWith: (...args: [...Parameters<T>] | MatchersOrLiterals<[...Parameters<T>]>) => jest.Mock<T>;
}

export type MockProxy<T> = {
[K in keyof T]: T[K] extends (...args: infer A) => infer B ? CalledWithMock<B, A> : T[K];
} &
T;
[K in keyof T]: T[K] extends FunctionLike ? CalledWithMock<T[K]> : T[K];
} & T;

export type DeepMockProxy<T> = {
// This supports deep mocks in the else branch
[K in keyof T]: T[K] extends (...args: infer A) => infer B ? CalledWithMock<B, A> : DeepMockProxy<T[K]>;
} &
T;
[K in keyof T]: T[K] extends FunctionLike ? CalledWithMock<T[K]> : DeepMockProxy<T[K]>;
} & T;

export type DeepMockProxyWithFuncPropSupport<T> = {
// This supports deep mocks in the else branch
[K in keyof T]: T[K] extends (...args: infer A) => infer B ? CalledWithMock<B, A> & DeepMockProxy<T[K]> : DeepMockProxy<T[K]>;
} &
T;
[K in keyof T]: T[K] extends FunctionLike ? CalledWithMock<T[K]> & DeepMockProxy<T[K]> : DeepMockProxy<T[K]>;
} & T;

export interface MockOpts {
deep?: boolean;
Expand Down Expand Up @@ -180,11 +179,7 @@ const mock = <T, MockedReturn extends MockProxy<T> & T = MockProxy<T> & T>(
return overrideMockImp(mockImplementation, opts);
};

export const mockFn = <
T extends Function,
A extends any[] = T extends (...args: infer AReal) => any ? AReal : any[],
R = T extends (...args: any) => infer RReal ? RReal : any
>(): CalledWithMock<R, A> & T => {
export const mockFn = <T extends FunctionLike>(): CalledWithMock<T> & T => {
// @ts-ignore
return calledWithFn();
};
Expand Down
3 changes: 0 additions & 3 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@
"noImplicitAny": true,
"strictNullChecks": true,
"alwaysStrict": true,
"types": [
"jest"
],
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
},
Expand Down

0 comments on commit da688eb

Please sign in to comment.