Skip to content

Commit

Permalink
Add support for providing fallbackMockImplementation
Browse files Browse the repository at this point in the history
  • Loading branch information
skovhus committed Feb 7, 2023
1 parent 5b472b3 commit 12358fd
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 7 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,19 @@ expect(mockObj.deepProp(1)).toBe(3);
expect(mockObj.deepProp.getNumber(1)).toBe(4);
```

Can can provide a fallback mock implementation used if you do not define a return value using `calledWith`.

```ts
import { mockDeep } from 'jest-mock-extended';
const mockObj = mockDeep<Test1>({
fallbackMockImplementation: () => {
throw new Error('please add expected return value using calledWith');
},
});
expect(() => mockObj.getNumber()).toThrowError('not mocked');
```


## Available Matchers


Expand Down
8 changes: 5 additions & 3 deletions src/CalledWithFn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,16 @@ const checkCalledWith = <T, Y extends any[]>(calledWithStack: CalledWithStackIte
return calledWithInstance ? calledWithInstance.calledWithFn(...actualArgs) : undefined;
};

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

(fn as CalledWithMock<T, Y>).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();
const calledWithFn = jest.fn(fallbackMockImplementation);
if (!fn.getMockImplementation()) {
// Our original function gets a mock implementation which handles the matching
fn.mockImplementation((...args: Y) => checkCalledWith(calledWithStack, args));
Expand Down
26 changes: 26 additions & 0 deletions src/Mock.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,32 @@ describe('jest-mock-extended', () => {
mockObj.deepProp.getNumber(2);
expect(mockObj.deepProp.getNumber).toHaveBeenCalledTimes(1);
});

test('fallback mock implementation can be overridden', () => {
const mockObj = mockDeep<Test1>({
fallbackMockImplementation: () => {
throw new Error('not mocked');
},
});
expect(() => mockObj.getNumber()).toThrowError('not mocked');
});

test('fallback mock implementation can be overridden while also providing a mock implementation', () => {
const mockObj = mockDeep<Test1>(
{
fallbackMockImplementation: () => {
throw new Error('not mocked');
},
},
{
getNumber: () => {
return 150;
},
}
);
expect(mockObj.getNumber()).toBe(150);
expect(() => mockObj.deepProp.getNumber(1)).toThrowError('not mocked');
});
});

describe('Deep mock support for class variables which are functions but also have nested properties and functions', () => {
Expand Down
16 changes: 12 additions & 4 deletions src/Mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export type DeepMockProxyWithFuncPropSupport<T> = {

export interface MockOpts {
deep?: boolean;
fallbackMockImplementation?: (...args: any[]) => any;
}

export const mockClear = (mock: MockProxy<any>) => {
Expand Down Expand Up @@ -94,10 +95,17 @@ export const mockReset = (mock: MockProxy<any>) => {
}
};

export function mockDeep<T>(opts: { funcPropSupport: true }, mockImplementation?: DeepPartial<T>): DeepMockProxyWithFuncPropSupport<T>;
export function mockDeep<T>(
opts: { funcPropSupport?: true; fallbackMockImplementation?: MockOpts['fallbackMockImplementation'] },
mockImplementation?: DeepPartial<T>
): DeepMockProxyWithFuncPropSupport<T>;
export function mockDeep<T>(mockImplementation?: DeepPartial<T>): DeepMockProxy<T>;
export function mockDeep(arg1: any, arg2?: any) {
return mock(arg1 && 'funcPropSupport' in arg1 ? arg2 : arg1, { deep: true });
const [opts, mockImplementation] =
typeof arg1 === 'object' && (typeof arg1.fallbackMockImplementation === 'function' || arg1.funcPropSupport === true)
? [arg1, arg2]
: [{}, arg1];
return mock(mockImplementation, { deep: true, fallbackMockImplementation: opts.fallbackMockImplementation });
}

const overrideMockImp = (obj: DeepPartial<any>, opts?: MockOpts) => {
Expand Down Expand Up @@ -125,7 +133,7 @@ const handler = (opts?: MockOpts) => ({
},

get: (obj: MockProxy<any>, property: ProxiedProperty) => {
let fn = calledWithFn();
let fn = calledWithFn({ fallbackMockImplementation: opts?.fallbackMockImplementation });

// @ts-ignore
if (!(property in obj)) {
Expand All @@ -148,7 +156,7 @@ const handler = (opts?: MockOpts) => ({
obj[property]._isMockObject = true;
} else {
// @ts-ignore
obj[property] = calledWithFn();
obj[property] = calledWithFn({ fallbackMockImplementation: opts?.fallbackMockImplementation });
}
}

Expand Down

0 comments on commit 12358fd

Please sign in to comment.