Skip to content

Commit

Permalink
fix(ts-sinon): fixed match all issue of jest mocks
Browse files Browse the repository at this point in the history
  • Loading branch information
Christian Jeschke committed Jun 28, 2024
1 parent 0392c39 commit e322bae
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 34 deletions.
37 changes: 25 additions & 12 deletions packages/testing/ts-jest/src/mocks.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface TestInterface {
someBool: boolean;
optional: string | undefined;
func: (num: number, str: string) => boolean;
func2: (entity: TestClass) => void;
}

class TestClass {
Expand Down Expand Up @@ -41,7 +42,7 @@ describe('Mocks', () => {
const result = mock.switchToHttp().getRequest();

expect(result).toBe(request);
expect(mock.switchToHttp).toBeCalledTimes(1);
expect(mock.switchToHttp).toHaveBeenCalledTimes(1);
});

it('should work with truthy values properties', () => {
Expand Down Expand Up @@ -83,8 +84,19 @@ describe('Mocks', () => {

const funcResult = mock.func(42, '42');
expect(funcResult).toBe(false);
expect(mock.func).toBeCalledTimes(1);
expect(mock.func).toBeCalledWith(42, '42');
expect(mock.func).toHaveBeenCalledTimes(1);
expect(mock.func).toHaveBeenCalledWith(42, '42');
});

it('should match mocked instances', () => {
const mock = createMock<TestInterface>();
const mockedInstance = createMock<TestClass>({ someProperty: 42 });

mock.func2(mockedInstance);
expect(mock.func2).toHaveBeenCalledWith(mockedInstance);
expect(mock.func2).not.toHaveBeenCalledWith(42);
expect(mock.func2).not.toHaveBeenCalledWith('42');
expect(mock.func2).not.toHaveBeenCalledWith(true);
});

it('should work with classes', () => {
Expand Down Expand Up @@ -121,7 +133,7 @@ describe('Mocks', () => {

const result = await mock.doSomethingAsync();
expect(result).toBe(42);
expect(mock.doSomethingAsync).toBeCalledTimes(1);
expect(mock.doSomethingAsync).toHaveBeenCalledTimes(1);
});

it('should work with unknown properties', () => {
Expand Down Expand Up @@ -156,8 +168,8 @@ describe('Mocks', () => {
const second = mock.switchToRpc();
const third = mock.switchToWs();

expect(mock.switchToRpc).toBeCalledTimes(2);
expect(mock.switchToWs).toBeCalledTimes(1);
expect(mock.switchToRpc).toHaveBeenCalledTimes(2);
expect(mock.switchToWs).toHaveBeenCalledTimes(1);
expect(first.getContext).toBeDefined();
expect(second.getContext).toBeDefined();
expect(third.getClient).toBeDefined();
Expand All @@ -173,7 +185,7 @@ describe('Mocks', () => {

const result = executionContextMock.switchToHttp().getRequest();
expect(result).toBe(request);
expect(httpArgsHost.getRequest).toBeCalledTimes(1);
expect(httpArgsHost.getRequest).toHaveBeenCalledTimes(1);
});

it('should automock promises so that they are awaitable', async () => {
Expand All @@ -185,7 +197,7 @@ describe('Mocks', () => {

const result = await mock.doSomethingAsync();
expect(result).toBeDefined();
expect(mock.doSomethingAsync).toBeCalledTimes(1);
expect(mock.doSomethingAsync).toHaveBeenCalledTimes(1);
});

it('should automock objects returned from automocks', () => {
Expand All @@ -198,8 +210,8 @@ describe('Mocks', () => {
expect(request1).toBe(request);
expect(request2).toBe(request);

expect(mock.switchToHttp).toBeCalledTimes(3);
expect(mock.switchToHttp().getRequest).toBeCalledTimes(2);
expect(mock.switchToHttp).toHaveBeenCalledTimes(3);
expect(mock.switchToHttp().getRequest).toHaveBeenCalledTimes(2);
});

it('should automock objects returned from automocks recursively', () => {
Expand Down Expand Up @@ -278,8 +290,9 @@ describe('Mocks', () => {
}).compile();

mockedProvider = module.get<DeepMocked<ExecutionContext>>(diToken);
dependentProvider =
module.get<{ dependent: () => string }>(dependentToken);
dependentProvider = module.get<{ dependent: () => string }>(
dependentToken
);
});

it('should correctly resolve mocked providers', async () => {
Expand Down
79 changes: 57 additions & 22 deletions packages/testing/ts-jest/src/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,34 +26,68 @@ export type DeepMocked<T> = {
const createRecursiveMockProxy = (name: string) => {
const cache = new Map<string | number | symbol, any>();

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

const checkProp = obj[prop];
if (
[
'_isMockFunction',
'mock',
'mockClear',
'mockImplementation',
'mockImplementationOnce',
'mockName',
'getMockName',
'getMockImplementation',
'mockRejectedValue',
'mockRejectedValueOnce',
'mockReset',
'mockResolvedValue',
'mockResolvedValueOnce',
'mockRestore',
'mockReturnThis',
'mockReturnValue',
'mockReturnValueOnce',
'withImplementation',
'calls',
].includes(propName)
) {
return Reflect.get(obj, prop, receiver);
}

const mockedProp =
prop in obj
? typeof checkProp === 'function'
? jest.fn()
: checkProp
: propName === 'then'
? undefined
: createRecursiveMockProxy(propName);
if (cache.has(prop)) {
return cache.get(prop);
}

const checkProp = obj[prop];

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

return mockedProp;
},
}
);
cache.set(prop, mockedProp);

return jest.fn(() => proxy);
return mockedProp;
},
});
};

export type MockOptions = {
Expand All @@ -72,6 +106,7 @@ export const createMock = <T extends object>(
if (
prop === 'inspect' ||
prop === 'then' ||
prop === 'asymmetricMatch' ||
(typeof prop === 'symbol' &&
prop.toString() === 'Symbol(util.inspect.custom)')
) {
Expand Down

0 comments on commit e322bae

Please sign in to comment.