In the process of developing a Jest utility, I have created a solution where an object mock is lazily generated as properties are accessed or called. Essentially, when a property is invoked like a method, it will utilize a jest.fn()
for that specific path within the object. On the other hand, if a property is accessed as a value and has not been explicitly set previously, another dynamic mock object will be created on that property in a recursive manner.
const foo = new DynamicMock()
foo.bar.baz.buzz('Hello') // buzz() becomes a jest.fn() when called
console.log(foo.bar.baz.buzz.mock.calls[0][0]) // 'Hello'
The utility I've developed leverages Proxy objects and performs as expected, but I'm encountering challenges with typing.
The primary goal of this utility is to establish a type that can fulfill object dependencies, such as interfaces, and simplify the mocking of dependencies during testing.
interface Foo {
bar(): void
}
let fooMock: DynamicMock<Foo>
beforeEach(() => {
fooMock = new DynamicMock()
})
it('Should', () => {
fooMock.bar // Should have type checking/completion for this property
})
I have devised a type signature that functions well initially but encounters issues with recursively nested types in the mocked types.
export type DynamicMock<T> = {
[K in keyof T]: T[K] &
(T[K] extends (...args: infer A) => infer B ? jest.Mock<B, A> : DynamicMock<T[K]>);
};
export type DynamicMockConstructor = {
new <T>(): DynamicMock<T>;
extend<T>(constructor: unknown): new <U extends T>() => DynamicMock<U>;
};
export const DynamicMock: DynamicMockConstructor;
declare module externalPackage {
export type JSONValue = null | void
| boolean | number | string | Array<JSONValue> | JSONObject;
export type JSONObject = {
[key: string]: JSONValue;
};
export interface Bar {
meta: JSONObject
}
export interface Foo {
getBars(): ReadonlyArray<Bar>
}
}
const mockFoo = new DynamicMock<externalPackage.Foo>()
const mockBar = new DynamicMock<externalPackage.Bar>()
mockFoo.getBars.mockReturnValue([mockBar]) // "Type instantiation is excessively deep and possibly infinite."
This triggers the error message
Type instantiation is excessively deep and possibly infinite.
due to the recursive JSONObject
type.
Given that I cannot modify the code from the external package, I am exploring options to enhance my DynamicMock
type definition to address this limitation.