Я создал простую вспомогательную фиктивную функцию. В настоящее время я борюсь с правильными типами. Вот что у меня есть до сих пор:
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;
}
Вот пример детской площадки.
TypeScript ругается в двух местах.
Во-первых, он жалуется на то, что implementation
говорит:
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'.
Во-вторых, он жалуется на mockFn.calls
что
Member 'calls' implicitly has an 'any[]' type.
Функцию mocked следует использовать следующим образом:
// 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
Но он также должен работать без реализации (отсюда и значение по умолчанию () => {}
).
const mockFn = fn();
// etc.
Было бы здорово, если бы TypeScript мог знать, что mockedFn
имеет ту же сигнатуру функции, что и originalFunction
, но дополнительно раскрывает .calls
, hasBeenCalled
и hasBeenCalledWith
.
В моей текущей реализации он, кажется, знает о hasBeenCalled
и hasBeenCalledWith
, говоря, что они типа:
mockFn.hasBeenCalled(): boolean
mockFn.hasBeenCalledWith(...args: Y): boolean
Как я могу исправить эти ошибки типа, чтобы TypeScript знал о возможностях fn
?
Вы можете использовать один общий параметр, представляющий функцию, и использовать с ним утилиты Parameters и ReturnType:
function fn<T extends (...args: any[]) => any> (implementation?: T) {
const mockFn = (...args: Parameters<T>): ReturnType<T> => {
mockFn.calls.push(args);
return implementation?.(...args);
};
mockFn.calls = [] as (Parameters<T>)[];
mockFn.hasBeenCalled = () => mockFn.calls.length > 0;
mockFn.hasBeenCalledWith = (...args: Parameters<T>) => includes(args, mockFn.calls);
return mockFn;
}
Первая ошибка говорит о том, что можно вызвать функцию с явными общими параметрами (например, fn<number, []>()
), а значение по умолчанию (() => {}
) не будет предоставлять требуемый тип возвращаемого значения. Чтобы исправить эту необязательную цепочку, вместо значения по умолчанию использовалось