I'm using fetch-mock
library to build my own utils.
When defining a new function and passing args to fetchMock.mock
function, I get this error:
A spread argument must either have a tuple type or be passed to a rest parameter
export function mockRequest(...args: any) {
return fetchMock.mock(...args);
}
I could strictly define the amount of arguments, but would want to avoid it. Any suggestions how to keep ...args
and make TypeScript happy?
I've tried various solutions such as using .apply and doing some casts as well but to no avail.
fetchMock.mock
is typed to accept 0-3 arguments:
mock(matcher: MockMatcher | MockOptions, response: MockResponse | MockResponseFunction, options?: MockOptions): this;
mock(options: MockOptions): this;
mock(): this;
– wheresrhys/fetch-mock/types/index.d.ts:L270-L294
An attempt is made to spread an array of any
into it. any
will not stop TypeScript from checking the potential length of the spread – it will only not check the type of the items in the tuple are acceptable for the function parameters. An example:
function fn(a: number, b: number) {}
const foo: any[] = [];
const bar: [any, any] = ["one", "two"];
fn(...foo); // Not OK ❌
// ~~~~~~
// A spread argument must either have a tuple type or be passed to a rest parameter.
fn(...bar); // OK, even though we're passing in strings ✅
The error message tells you the solution (emphasis mine):
A spread argument must either have a tuple type or be passed to a rest parameter.
Let's explore both of them.
If the function is typed to accept a rest parameter it works because rest parameters are essentially an infinite list of optional parameters. However, in your case, you don't have control of the interface of the library. Consequently, you'll have to use the other option.
Tuples are a list that have two notable properties. They are:
Parameters
?tl;dr this doesn't work, see the linked GitHub issues
TypeScript provides a utility function named Parameters
which constructs a tuple type from the types used in the parameters of the function passed to it, so in theory you could do this:
function mockRequest(...args: Parameters<typeof fetchMock.mock>) {
return fetchMock.mock(...args);
}
However at the time of writing, Parameters
only returns the parameters from the last overload (see TypeScript issue 14107 and TypeScript issue 32164). In the above example, mockRequest
would be typed to accept no parameters because the last overload of fetchMock.mock
has no parameters.
As lovely as it would've been to use Parameters
. It looks like we'll have to manually add types. Here's a working example:
type Overload1 = [matcherOrOptions: MockMatcher | MockOptions, response: MockResponse | MockResponseFunction, options?: MockOptions];
type Overload2 = [matcherOrOptions: MockOptions];
type Overload3 = [];
type Args = Overload1 | Overload2 | Overload3;
function mockRequest(...args: Args) {
return args.length === 1 ?
fetchMock.mock(...args) :
args.length >= 2 ?
fetchMock.mock(...args as Overload1) :
fetchMock.mock();
}
For an underdetermined reason, TypeScript isn't able to narrow the type of args
to Overload1
with the condition args.length >= 2
so a cast is necessary.