I created a simple helper mock function. I'm currently struggling with getting the types right. Here is what I have so far:
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 a playground example.
TypeScript complains at two places.
Firstly, it complains about implementation
saying:
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 complains about mockFn.calls
that
Member 'calls' implicitly has an 'any[]' type.
The mocked function should be used 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
But it should also work without implementation (hence the default to () => {}
).
const mockFn = fn();
// etc.
It would be cool if TypeScript could know that mockedFn
has the same function signature as originalFunction
, but additionally exposing .calls
, hasBeenCalled
and hasBeenCalledWith
.
In my current implementation, it seems to know about hasBeenCalled
and hasBeenCalledWith
saying they are of type:
mockFn.hasBeenCalled(): boolean
mockFn.hasBeenCalledWith(...args: Y): boolean
How can I fix these type errors so that TypeScript knows about fn
s capabilities?
You could use single generic parameter representing the function and use Parameters and ReturnType utilities with it:
Playground
First error says that it is possible to call function with explicit generic parameters (e.g.
fn<number, []>()
) and the default value (() => {}
) won't supply the required return type. To fix this optional chaining was used instead of default value