Skip to content

Commit c2923a8

Browse files
authored
Merge pull request #110 from skovhus/add-fallback-mock-implementation
Add support for providing fallbackMockImplementation
2 parents 5b472b3 + c9b3491 commit c2923a8

File tree

4 files changed

+82
-8
lines changed

4 files changed

+82
-8
lines changed

README.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,19 @@ describe('Party Tests', () => {
5050

5151
expect(mock.getPartyType()).toBe('west coast party');
5252
});
53-
});
53+
54+
test('throwing an error if we forget to specify the return value')
55+
const mock = mock<PartyProvider>(
56+
{},
57+
{
58+
fallbackMockImplementation: () => {
59+
throw new Error('not mocked');
60+
},
61+
}
62+
);
63+
64+
expect(() => mock.getPartyType()).toThrowError('not mocked');
65+
});
5466
```
5567

5668
## Assigning Mocks with a Type
@@ -141,6 +153,19 @@ expect(mockObj.deepProp(1)).toBe(3);
141153
expect(mockObj.deepProp.getNumber(1)).toBe(4);
142154
```
143155

156+
Can can provide a fallback mock implementation used if you do not define a return value using `calledWith`.
157+
158+
```ts
159+
import { mockDeep } from 'jest-mock-extended';
160+
const mockObj = mockDeep<Test1>({
161+
fallbackMockImplementation: () => {
162+
throw new Error('please add expected return value using calledWith');
163+
},
164+
});
165+
expect(() => mockObj.getNumber()).toThrowError('not mocked');
166+
```
167+
168+
144169
## Available Matchers
145170

146171

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: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,19 @@ describe('jest-mock-extended', () => {
124124
expect(mockObj.getSomethingWithArgs(1, 2)).toBe(1);
125125
});
126126

127+
test('Can specify fallbackMockImplementation', () => {
128+
const mockObj = mock<MockInt>(
129+
{},
130+
{
131+
fallbackMockImplementation: () => {
132+
throw new Error('not mocked');
133+
},
134+
}
135+
);
136+
137+
expect(() => mockObj.getSomethingWithArgs(1, 2)).toThrowError('not mocked');
138+
});
139+
127140
test('Can specify multiple calledWith', () => {
128141
const mockObj = mock<MockInt>();
129142
mockObj.getSomethingWithArgs.calledWith(1, 2).mockReturnValue(3);
@@ -308,6 +321,32 @@ describe('jest-mock-extended', () => {
308321
mockObj.deepProp.getNumber(2);
309322
expect(mockObj.deepProp.getNumber).toHaveBeenCalledTimes(1);
310323
});
324+
325+
test('fallback mock implementation can be overridden', () => {
326+
const mockObj = mockDeep<Test1>({
327+
fallbackMockImplementation: () => {
328+
throw new Error('not mocked');
329+
},
330+
});
331+
expect(() => mockObj.getNumber()).toThrowError('not mocked');
332+
});
333+
334+
test('fallback mock implementation can be overridden while also providing a mock implementation', () => {
335+
const mockObj = mockDeep<Test1>(
336+
{
337+
fallbackMockImplementation: () => {
338+
throw new Error('not mocked');
339+
},
340+
},
341+
{
342+
getNumber: () => {
343+
return 150;
344+
},
345+
}
346+
);
347+
expect(mockObj.getNumber()).toBe(150);
348+
expect(() => mockObj.deepProp.getNumber(1)).toThrowError('not mocked');
349+
});
311350
});
312351

313352
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)