Using TypeScript to define the types of a simulated function

I have developed a helper mock function but I am facing issues with getting the types right. The current implementation is as follows:

import includes from 'ramda/src/includes';

function fn<T, Y extends any[]> (implementation: ((...args: Y) => T) = () => {}) {
  const mockFn = (...args: Y) => {
    mockFn.calls.push(args);
    return implementation(...args);
  };
  mockFn.calls = [];

  mockFn.hasBeenCalled = () => mockFn.calls.length > 0;
  mockFn.hasBeenCalledWith = (...args: Y) => includes(args, mockFn.calls);

  return mockFn;
}

Here is an example in the playground.

TypeScript raises two complaints.

Firstly, it objects to implementation stating:

Type '() => void' is not assignable to type '(...args: Y) => T'.
  Type 'void' is not assignable to type 'T'.
    'T' could be instantiated with an arbitrary type which could be unrelated to 'void'.

Secondly, it points out an issue with mockFn.calls:

Member 'calls' implicitly has an 'any[]' type.

The usage of the mocked function should be like this:

// with implementation
const originalFunction = (a: number, b: number) => a + b; // e.g. a simple add function
const mockedFn = fn(originalFunction);

mockedFn.hasBeenCalled();
// false

mockedFn(21, 21);
// 42

mockedFn.hasBeenCalled();
// true

mockedFn.hasBeenCalledWith(21);
// false

mockedFn.hasBeenCalledWith(21, 21);
// true

It should also work without implementation (defaulting to () => {}).

const mockFn = fn();

// etc.

It would be great if TypeScript could recognize that mockedFn has the same function signature as originalFunction, while also exposing .calls, hasBeenCalled, and hasBeenCalledWith.

In the current implementation, TypeScript seems aware of hasBeenCalled and hasBeenCalledWith, defining them as:

mockFn.hasBeenCalled(): boolean
mockFn.hasBeenCalledWith(...args: Y): boolean

How can I resolve these type errors so that TypeScript acknowledges the capabilities of fn?

Answer №1

One approach is to utilize a single generic placeholder that represents the function and employ the Parameters and ReturnType utilities in conjunction with it:

function func<T extends (...args: any[]) => any> (customFn?: T) {
  const mockFunc = (...args: Parameters<T>): ReturnType<T> => {
    mockFunc.invocations.push(args);
    return customFn?.(...args);
  };
  mockFunc.invocations = [] as (Parameters<T>)[];

  mockFunc.hasBeenCalled = () => mockFunc.invocations.length > 0;
  mockFunc.hasBeenCalledWith = (...args: Parameters<T>) => includes(args, mockFunc.invocations);

  return mockFunc;
}

Interactive Environment


An initial issue raised concerns the ability to call the function with explicit type parameters (e.g., func<number, []>()) where the default value (() => {}) fails to provide the necessary return type. This was mitigated by incorporating optional chaining instead of relying on a default value.

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

Error in Protractor: Headless Chrome fails to download file

Having trouble downloading a file using Protractor with Headless Chrome. The download works fine when not in headless mode. In my protractor.conf.js file, I have the following capabilities configured: capabilities: { 'browserName': 'chrome ...

Difficulty in updating the value of HTML Range element using Angular6 function

My goal is to retrieve the changing values from a series of HTML range sliders. This is how they are set up: <li *ngFor="let rgbVal of rgbVals; let i=index"> {{i}}: {{rgbVal}} <br> <div class="color-box" [style.back ...

Issue with validating the date picker when updating user information

Trying to manage user accounts through a dialog form for adding and updating operations, where the type of operation is determined by a variable injected from the main component. Encountered an issue while launching the update process - the date picker tri ...

Grab the content from a contenteditable HTML Element using React

Currently, I am developing an EditableLabel React component using Typescript in conjunction with the contenteditable attribute. My goal is to enable the selection of the entire text content when the user focuses on it, similar to the behavior showcased in ...

The variable 'key' is declared in the Google Chrome extension V3 local storage, but it appears that its assigned value is never actually accessed

In my TypeScript code, I have defined a function like so: setLocalStorage: async (key: string, value: string): Promise<string> => { return new Promise((resolve, reject) => { chrome.storage.local.set({ key: value }, funct ...

Error: When trying to run the `ng build` command in Git Bash, TypeScript module cannot be

When attempting to execute ng build using Git Bash, I encountered this error message, even though I had previously executed npm install -g typescript. Where should I place the typescript installation so that Git can detect it? Error $ npm install -g typ ...

Accessing properties through recursive type aliases

I am attempting to transcribe this class originally written in JavaScript. Here is the code snippet provided. type SchemaDefinition<Schema> = {[Key in keyof Schema]: Schema[Key][] | {[K in keyof Schema[Key]]: SchemaDefinition<Schema[Key][K]>}} ...

What is the process for managing multiple selections using checkbox code?

I have been following the official tutorial for ag-grid and I've reached a point where I need to manipulate information related to selected checkboxes. However, the documentation lacks detail on how the code actually functions. It might be understanda ...

Encountering: Unable to break down the property 'DynamicServerError' of 'serverHooks' as it does not have a defined value

An error has arisen in a Nextjs app with TypeScript, specifically in the line of my react component which can be found here. This is my inaugural package creation and after several trials, I managed to test it successfully in a similar vite and TypeScript ...

What are the mechanisms behind default values and union types in React components?

Providing clear examples of my inquiry is essential: https://codesandbox.io/s/gracious-diffie-hs57tc?file=/src/App.tsx My curiosity lies in understanding why setting the default value to a prop disrupts type conditions. Additionally, I am puzzled by the ...

Guide on how to retrieve the information stored in an object

I am experiencing an issue with my function that retrieves data from Firebase. I am able to read the objects, but I cannot access the properties within them. Whenever I try to parse the content, an error occurs. Here is the function in question: this ...

Io-ts interface for handling properties with unspecified keys

I'm currently working on implementing an io-ts interface that resembles the following: export interface myInterface { [key:string]?: string | undefined | null } My objective is to convert this into its io-ts counterpart. Ultimately, I plan to merge ...

Typescript is unable to mandate the passing of generics

I am in need of a utility that can handle generic object types, taking a key belonging to that type and the associated property like this: export type StateBuilder = <StateSchema, Keys extends keyof StateSchema>( key: Keys, data: StateSchema[Keys ...

When utilizing the Turf.nearPoint() function, it is important to consider the type of point being used. The documentation for Turf.nearestPoint() appears to be inaccurate

I have some code that needs to be transcribed from another system, so unfortunately I can't easily share it here. If I could, I would just post the entire project. Recently, I attempted to integrate into our project but encountered error messages. T ...

The library "vue-property-decorator" (v10.X) is causing issues with resolving in Webpack despite being successfully installed

Encountered an error message Module not found: Error: Can't resolve './decorators/Emit' while attempting to import functionality from the library vue-property-decorator. The package is installed and accessible, ruling out a simple installati ...

"Exploring the process of creating a custom type by incorporating changes to an existing interface

One of the challenges I'm facing is defining an object based on a specific interface structure. The interface I have looks like this: interface Store { ReducerFoo : ReducerFooState; ReducerBar : ReducerBarState; ReducerTest : ReducerTestSt ...

When using NodeJS and TypeScript, the URL query function may encounter issues when multiple parameters are included

This is the code snippet for a controller in my route: export const getHotels = async ( req: Request, res: Response, next: NextFunction ) => { try { const hotels = await Hotel.find(req.query).limit(+req.query.limit); res.status(200).json ...

What's Preventing TypeScript Enum Keys from Being Transformed during Compilation?

I've encountered an issue while working on a project with TypeScript, Webpack, Babel, and React. The problem arises when trying to use enum members as keys for an object. Here's a snippet of the problematic file: // traits.ts import { Trait } fr ...

Is there a way to run TypeScript code without transpiling it first?

Upon delving into TypeScript, I quickly realized that node.js doesn't directly run TypeScript code, requiring the use of a TypeScript compiler to convert it into JavaScript. After some exploration, I stumbled upon ts-node (TypeScript execution and RE ...

Can Typescript provide an array of the different types of values in a record?

I am interested in obtaining all the various types of values within a record. For instance, if I have an object structured like this: { 'a': 1, 'b': false } What I aim to achieve is having a type that includes number and boolean. ...