Recursive type analysis indicates that the instantiation of the type is excessively deep and may lead to potential infinite loops

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.

TypeScript Playground

Answer â„–1

One effective strategy for handling scenarios like this is to incorporate a depth limiter into your recursive type definition. This way, the recursion can stop after reaching a certain depth when dealing with other deeply nested or recursive types. Here's an example of how you could modify your code to include a depth limiter:

type DynamicMock<T, D extends number = 9, DA extends any[] = []> = {
    [K in keyof T]: T[K] &
    (T[K] extends (...args: infer A) => infer B ? jest.Mock<B, A> :
        D extends DA['length'] ? any : DynamicMock<T[K], D, [0, ...DA]>);
};

The generic type parameter D represents the maximum depth allowed for recursion. By default, it's set to 9, but you can adjust this value as needed.

Although TypeScript doesn't support direct mathematical operations on numeric literal types, we can leverage variadic tuple types to simulate counting up to D. The length of the accumulator array DA helps control the recursion depth and termination conditions.

If the length of DA equals D, the base case is reached, and the recursion stops. Otherwise, we continue by extending DA with another element ([0, ...DA]) and recurse further.


Applying this modification to your code snippet resolves the issue at hand as intended.

It's important to note that while this approach may work for many scenarios, deeply recursive utility types can present unexpected challenges and edge cases. It's recommended to thoroughly test such utilities across various use cases before incorporating them into production-level code bases.

Playground link containing the modified code

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Troubleshoot TypeScript API within Angular 12 using Chrome debugger

I'm having trouble finding a solution on SO. Can anyone help me debug and check the code snippet below for hello? getHero(): void { const id = parseInt(this.route.snapshot.paramMap.get('id') !, 10); this.heroService.getHero(id) .sub ...

Trouble arises when trying to import Jest with Typescript due to SyntaxError: Import statement cannot be used outside a module

Whenever I execute Jest tests using Typescript, I encounter a SyntaxError while importing an external TS file from a node_modules library: SyntaxError: Cannot use import statement outside a module I'm positive that there is a configuration missing, b ...

The canDeactivate function in the Angular 2 router can modify the router state even if I choose to cancel an action in the confirmation popup

In my Angular 2 project, I have implemented the canDeactivate method to prevent browser navigation. When a user tries to leave the page, a confirmation popup is displayed. However, if the user chooses to cancel, the router still changes its state even th ...

Having trouble with errors when trying to implement react-router-v6 with typescript

Having trouble with my code and receiving the following error: "Argument of type 'HTMLElement | null' is not assignable to parameter of type 'Element | DocumentFragment'. Type 'null' is not assignable to type 'Element | ...

Error in Deno pooling map server due to unhandled AggregateError

I am trying to run this TypeScript code snippet import { serve } from "https://deno.land/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="eb989f8d9cdbc5dbd9dac5db">[email protected]</a>/http/server.ts" imp ...

Managing API responses using Redux and Typescript

As a beginner in Typescript, I am struggling to integrate Redux with it. The documentation on using Redux with Typescript is confusing me. I am attempting to fetch data and dispatch it to my reducer for future use, just as I did before adopting Typescript ...

The synchronization between Typescript and the HTML view breaks down

I am currently working on an application that retrieves user event posts from MongoDB and displays them in HTML. In the Event-post.ts file, inside the ngOnInit() function, I have written code to retrieve the posts using the postsService.getPosts() method. ...

Using Angular to bind a click event to an element after it has been compiled

I am currently developing an application for students using Angular 5. In this application, users can access and view various documents. When a user enters a document, a set of tools, including a marker tool, are displayed. This marker tool allows users to ...

Implementing conditional where clauses in Firestore queries with dynamic parameters

Consider this scenario: I have a dynamic filter list for my product list, and I want to send an HTTPS request to a cloud function based on the selected filters. However, when trying to set multiple conditional where clauses from that request... The multip ...

The operation of fetching multiple documents within a transaction loop is not functioning as anticipated

I am encountering an issue where I am attempting to retrieve multiple documents within a transaction and then update them all in the same transaction (due to their interdependence). Despite following the rule of ensuring all reads occur before any writes, ...

​Troubleshooting findOneAndUpdate in Next.js without using instances of the class - still no success

After successfully connecting to my MongoDB database and logging the confirmation, I attempted to use the updateUser function that incorporates findOneAndUpdate from Mongoose. Unfortunately, I ran into the following errors: Error: _models_user_model__WEBPA ...

Interactive feature on Google Maps information window allowing navigation to page's functions

Working on an Angular2 / Ionic 2 mobile App, I am utilizing the google maps JS API to display markers on a map. Upon clicking a marker, an information window pops up containing text and a button that triggers a function when clicked. If this function simpl ...

Precise object mapping with Redux and Typescript

In my redux slice, I have defined a MyState interface with the following structure: interface MyState { key1: string, key2: boolean, key3: number, } There is an action named set which has this implementation: set: (state: MyState, action: PayloadAct ...

Angular sub-route is failing to activate

My current setup involves Angular routing along with the use of ngx-translate-router, and I've encountered an unusual issue with child routes. It's unclear whether this problem is connected to the translated router module I'm utilizing, but ...

Encountering an issue while trying to load a file from an API due to difficulties in parsing it to

When trying to load an xlsx file from an API, I encountered an error because Angular automatically tries to parse the body as JSON. To resolve this issue, I found that specifying the response type directly in the request works: this.http.get(this.url + " ...

Using rxjs for exponential backoff strategy

Exploring the Angular 7 documentation, I came across a practical example showcasing the usage of rxjs Observables to implement an exponential backoff strategy for an AJAX request: import { pipe, range, timer, zip } from 'rxjs'; import { ajax } f ...

When the boolean is initially set to false, it will return true in an if statement without using

My Angular component contains a component-level boolean variable and an onClick event. Here's what the HTML file looks like: <div class="divClass" (click)="onClick($event)"></div> The relevant code from the TypeScript file is as follows: ...

Tips for maintaining a healthy balance of tasks in libuv during IO operations

Utilizing Typescript and libuv for IO operations is crucial. In my current situation, I am generating a fingerprint hash of a particular file. Let's say the input file size is approximately 1TB. To obtain the file's fingerprint, one method involv ...

Jest identifies active handles when executing synchronous scrypt operations in the crypto module of Node.js

During the execution of a unit test using the scryptSync function from the crypto package, I am encountering error messages and warnings that are unfamiliar to me. For instance, here is an example of a unit test I am running in Jest: it('Should match ...

Typescript: Shifting an image to the left and then returning it to the right

As a newcomer to Typescript, JavaScript, and front-end development, I am experimenting with creating a simulation of an AI opponent's "thinking" process when playing cards in a game. The idea is to visually represent the AI's decision-making by s ...