Problem
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 ✅
TypeScript Playground
Solution
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.
Passing to a rest parameter
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.
Type the argument as a tuple
Tuples are a list that have two notable properties. They are:
- Finite. This guarantees the length will match the length of the arguments to the function you're attempting to use.
- Ordered. This guarantees the type of a value at each index will match with the function's argument at the same index.
Using 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);
}
TypeScript Playground
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.
Manual types
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();
}
TypeScript Playground
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.