如何从泛型获取实例类型?修复TypeScript测试助手类型问题
问题背景
现有以下错误类定义:
export class ValidationFailedError extends Error { public errors: ValidationError[]; constructor(errors: ValidationError[]) { super('Validation errors exist'); this.errors = errors; } } export class ValidationError {} export class DuplicateFileValidationError extends ValidationError {} export class MultipleTransactionTypesValidationError extends ValidationError {} export class FileIsPlacedInWrongFolderValidationError extends ValidationError { constructor(public fileName: string, public expectedFolder: string, public actualFolder: string) { super(); } } export class InvalidTransactionsCountValidationError extends ValidationError { constructor( public transactionsCount: number, public minTransactionsCount: number, public maxTransactionsCount: number, ) { super(); } }
为简化Jest测试中对ValidationFailedError的验证,编写了如下测试助手函数,意图验证错误仅包含指定类型的单个ValidationError,并支持自定义断言回调:
function expectValidationFailedErrorContainsSingleError<T extends ValidationError>( validationFailedError: ValidationFailedError, errorCtor: T, customAssertAction?: (err: T) => void, ): void { const { errors } = validationFailedError; expect(Array.isArray(errors)).toBe(true); expect(errors.length).toEqual(1); const validationError = errors[0] as T; expect(validationError).toBeInstanceOf(errorCtor); customAssertAction(validationError); }
但在测试中使用时,TypeScript无法识别回调参数的实例类型:
expectValidationFailedErrorContainsSingleError( err, FileIsPlacedInWrongFolderValidationError, (validationError) => { expect(validationError.fileName).toEqual('test-file.json'); expect(validationError.actualFolder).toEqual(actualFolder); expect(validationError.expectedFolder).toEqual(expectedFolder); }, );
抛出错误:
Property 'fileName' does not exist on type 'typeof FileIsPlacedInWrongFolderValidationError'.ts(2339)
Property 'actualFolder' does not exist on type 'typeof FileIsPlacedInWrongFolderValidationError'.ts(2339)
Property 'expectedFolder' does not exist on type 'typeof FileIsPlacedInWrongFolderValidationError'.ts(2339)
鼠标悬停显示validationError的类型为typeof FileIsPlacedInWrongFolderValidationError(类构造函数类型),而非实例类型。
错误原因
原助手函数的泛型参数T extends ValidationError限定的是实例类型,但传入的errorCtor参数是类的构造函数(类型为typeof ValidationError的子类),导致TypeScript错误地将回调参数的类型推断为构造函数类型,而非实例类型。
修复方案
调整泛型参数,让T代表错误实例类型,同时将errorCtor的类型指定为该实例的构造函数类型。修改后的助手函数如下:
function expectValidationFailedErrorContainsSingleError<T extends ValidationError>( validationFailedError: ValidationFailedError, errorCtor: new (...args: any[]) => T, // 指定为T的构造函数类型 customAssertAction?: (err: T) => void, ): void { const { errors } = validationFailedError; expect(Array.isArray(errors)).toBe(true); expect(errors.length).toEqual(1); const validationError = errors[0] as T; expect(validationError).toBeInstanceOf(errorCtor); customAssertAction?.(validationError); // 用可选链避免未传回调时报错 }
验证效果
修改后,测试代码中的validationError会被正确推断为FileIsPlacedInWrongFolderValidationError实例类型,能够正常访问fileName、actualFolder、expectedFolder属性,TypeScript不再抛出类型错误。
内容的提问来源于stack exchange,提问作者Andrei Khotko

