Skip to content

Commit 12358fd

Browse files
committed
Add support for providing fallbackMockImplementation
1 parent 5b472b3 commit 12358fd

File tree

4 files changed

+56
-7
lines changed

4 files changed

+56
-7
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,19 @@ expect(mockObj.deepProp(1)).toBe(3);
141141
expect(mockObj.deepProp.getNumber(1)).toBe(4);
142142
```
143143

144+
Can can provide a fallback mock implementation used if you do not define a return value using `calledWith`.
145+
146+
```ts
147+
import { mockDeep } from 'jest-mock-extended';
148+
const mockObj = mockDeep<Test1>({
149+
fallbackMockImplementation: () => {
150+
throw new Error('please add expected return value using calledWith');
151+
},
152+
});
153+
expect(() => mockObj.getNumber()).toThrowError('not mocked');
154+
```
155+
156+
144157
## Available Matchers
145158

146159

src/CalledWithFn.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,16 @@ const checkCalledWith = <T, Y extends any[]>(calledWithStack: CalledWithStackIte
3232
return calledWithInstance ? calledWithInstance.calledWithFn(...actualArgs) : undefined;
3333
};
3434

35-
export const calledWithFn = <T, Y extends any[]>(): CalledWithMock<T, Y> => {
36-
const fn: jest.Mock<T, Y> = jest.fn();
35+
export const calledWithFn = <T, Y extends any[]>({
36+
fallbackMockImplementation,
37+
}: { fallbackMockImplementation?: (...args: Y) => T } = {}): CalledWithMock<T, Y> => {
38+
const fn: jest.Mock<T, Y> = jest.fn(fallbackMockImplementation);
3739
let calledWithStack: CalledWithStackItem<T, Y>[] = [];
3840

3941
(fn as CalledWithMock<T, Y>).calledWith = (...args) => {
4042
// We create new function to delegate any interactions (mockReturnValue etc.) to for this set of args.
4143
// If that set of args is matched, we just call that jest.fn() for the result.
42-
const calledWithFn = jest.fn();
44+
const calledWithFn = jest.fn(fallbackMockImplementation);
4345
if (!fn.getMockImplementation()) {
4446
// Our original function gets a mock implementation which handles the matching
4547
fn.mockImplementation((...args: Y) => checkCalledWith(calledWithStack, args));

src/Mock.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,32 @@ describe('jest-mock-extended', () => {
308308
mockObj.deepProp.getNumber(2);
309309
expect(mockObj.deepProp.getNumber).toHaveBeenCalledTimes(1);
310310
});
311+
312+
test('fallback mock implementation can be overridden', () => {
313+
const mockObj = mockDeep<Test1>({
314+
fallbackMockImplementation: () => {
315+
throw new Error('not mocked');
316+
},
317+
});
318+
expect(() => mockObj.getNumber()).toThrowError('not mocked');
319+
});
320+
321+
test('fallback mock implementation can be overridden while also providing a mock implementation', () => {
322+
const mockObj = mockDeep<Test1>(
323+
{
324+
fallbackMockImplementation: () => {
325+
throw new Error('not mocked');
326+
},
327+
},
328+
{
329+
getNumber: () => {
330+
return 150;
331+
},
332+
}
333+
);
334+
expect(mockObj.getNumber()).toBe(150);
335+
expect(() => mockObj.deepProp.getNumber(1)).toThrowError('not mocked');
336+
});
311337
});
312338

313339
describe('Deep mock support for class variables which are functions but also have nested properties and functions', () => {

src/Mock.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export type DeepMockProxyWithFuncPropSupport<T> = {
4949

5050
export interface MockOpts {
5151
deep?: boolean;
52+
fallbackMockImplementation?: (...args: any[]) => any;
5253
}
5354

5455
export const mockClear = (mock: MockProxy<any>) => {
@@ -94,10 +95,17 @@ export const mockReset = (mock: MockProxy<any>) => {
9495
}
9596
};
9697

97-
export function mockDeep<T>(opts: { funcPropSupport: true }, mockImplementation?: DeepPartial<T>): DeepMockProxyWithFuncPropSupport<T>;
98+
export function mockDeep<T>(
99+
opts: { funcPropSupport?: true; fallbackMockImplementation?: MockOpts['fallbackMockImplementation'] },
100+
mockImplementation?: DeepPartial<T>
101+
): DeepMockProxyWithFuncPropSupport<T>;
98102
export function mockDeep<T>(mockImplementation?: DeepPartial<T>): DeepMockProxy<T>;
99103
export function mockDeep(arg1: any, arg2?: any) {
100-
return mock(arg1 && 'funcPropSupport' in arg1 ? arg2 : arg1, { deep: true });
104+
const [opts, mockImplementation] =
105+
typeof arg1 === 'object' && (typeof arg1.fallbackMockImplementation === 'function' || arg1.funcPropSupport === true)
106+
? [arg1, arg2]
107+
: [{}, arg1];
108+
return mock(mockImplementation, { deep: true, fallbackMockImplementation: opts.fallbackMockImplementation });
101109
}
102110

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

127135
get: (obj: MockProxy<any>, property: ProxiedProperty) => {
128-
let fn = calledWithFn();
136+
let fn = calledWithFn({ fallbackMockImplementation: opts?.fallbackMockImplementation });
129137

130138
// @ts-ignore
131139
if (!(property in obj)) {
@@ -148,7 +156,7 @@ const handler = (opts?: MockOpts) => ({
148156
obj[property]._isMockObject = true;
149157
} else {
150158
// @ts-ignore
151-
obj[property] = calledWithFn();
159+
obj[property] = calledWithFn({ fallbackMockImplementation: opts?.fallbackMockImplementation });
152160
}
153161
}
154162

0 commit comments

Comments
 (0)