Skip to content

Commit

Permalink
fix(ts-jest): ts-jest: asymmetricMatch should be undefined for nested…
Browse files Browse the repository at this point in the history
… mocks as well
  • Loading branch information
rbnayax committed Aug 15, 2024
1 parent fd52dc6 commit 100d1be
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 60 deletions.
6 changes: 6 additions & 0 deletions packages/testing/ts-jest/src/mocks.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,12 @@ describe('Mocks', () => {
expect(mock.nested.toString()).toEqual('function () { [native code] }');
});

it('asymmetricMatch should not be set', () => {
const mock = createMock<any>();
expect(mock.asymmetricMatch).toBeUndefined();
expect(mock.nested.asymmetricMatch).toBeUndefined();
});

it('nested properties mocks should be able to set properties and override cache', () => {
const mock = createMock<any>();
const autoMockedFn = mock.nested.f;
Expand Down
100 changes: 40 additions & 60 deletions packages/testing/ts-jest/src/mocks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { jest } from '@jest/globals';
import { Mock } from 'jest-mock';

type DeepPartial<T> = {
[P in keyof T]?: T[P] extends Array<infer U>
Expand Down Expand Up @@ -45,78 +46,32 @@ const jestFnProps = new Set([
'calls',
]);

const createRecursiveMockProxy = (name: string) => {
const cache = new Map<string | number | symbol, any>();

const t = jest.fn();
return new Proxy(t, {
apply: (target, thisArg, argsArray) => {
const result = Reflect.apply(target, thisArg, argsArray);
if (target.getMockImplementation() || result) {
return result;
} else {
if (!cache.has('__apply')) {
cache.set('__apply', createRecursiveMockProxy(name));
}
return cache.get('__apply');
}
},
get: (obj, prop, receiver) => {
const propName = prop.toString();

if (jestFnProps.has(propName)) {
return Reflect.get(obj, prop, receiver);
}

if (cache.has(prop)) {
return cache.get(prop);
}

const checkProp = obj[prop];

const mockedProp =
prop in obj
? typeof checkProp === 'function'
? jest.fn(checkProp)
: checkProp
: propName === 'then'
? undefined
: createRecursiveMockProxy(propName);

cache.set(prop, mockedProp);

return mockedProp;
},
set: (obj, prop, newValue) => {
cache.set(prop, newValue);
return Reflect.set(obj, prop, newValue);
},
});
};

export type MockOptions = {
name?: string;
};

export const createMock = <T extends object>(
partial: PartialFuncReturn<T> = {},
options: MockOptions = {}
): DeepMocked<T> => {
const createProxy: {
<T extends object>(name: string, base: T): T;
<T extends Mock<any, any> = Mock<any, any>>(name: string): T;
} = <T extends object | Mock<any, any>>(name: string, base?: T): T => {
const cache = new Map<string | number | symbol, any>();
const { name = 'mock' } = options;
const handler: ProxyHandler<T> = {
get: (obj, prop, receiver) => {
const propName = prop.toString();

const proxy = new Proxy(partial, {
get: (obj, prop) => {
if (
prop === 'inspect' ||
prop === 'then' ||
prop === 'asymmetricMatch' ||
(typeof prop === 'symbol' &&
prop.toString() === 'Symbol(util.inspect.custom)')
(typeof prop === 'symbol' && propName === 'Symbol(util.inspect.custom)')
) {
return undefined;
}

if (!base && jestFnProps.has(propName)) {
return Reflect.get(obj, prop, receiver);
}

if (cache.has(prop)) {
return cache.get(prop);
}
Expand All @@ -131,7 +86,7 @@ export const createMock = <T extends object>(
} else if (prop === 'constructor') {
mockedProp = () => undefined;
} else {
mockedProp = createRecursiveMockProxy(`${name}.${prop.toString()}`);
mockedProp = createProxy(`${name}.${propName}`);
}

cache.set(prop, mockedProp);
Expand All @@ -142,7 +97,32 @@ export const createMock = <T extends object>(

return Reflect.set(obj, prop, newValue);
},
});
};
if (!base) {
(handler as ProxyHandler<Mock<any, any>>).apply = (
target,
thisArg,
argsArray
) => {
const result = Reflect.apply(target, thisArg, argsArray);
if (target.getMockImplementation() || result) {
return result;
} else {
if (!cache.has('__apply')) {
cache.set('__apply', createProxy(name));
}
return cache.get('__apply');
}
};
}
return new Proxy(base || (jest.fn() as T), handler);
};

export const createMock = <T extends object>(
partial: PartialFuncReturn<T> = {},
options: MockOptions = {}
): DeepMocked<T> => {
const { name = 'mock' } = options;
const proxy = createProxy<T>(name, partial as T);
return proxy as DeepMocked<T>;
};

0 comments on commit 100d1be

Please sign in to comment.