如何使用Jest和TypeScript模拟SSM.getParameter的重载签名?
我正在尝试模拟一个从AWS SSM参数存储获取值的单例类函数,getParameter()的类型定义如下:
getParameter(params: SSM.Types.GetParameterRequest, callback?: (err: AWSError, data: SSM.Types.GetParameterResult) => void): Request<SSM.Types.GetParameterResult, AWSError>; getParameter(callback?: (err: AWSError, data: SSM.Types.GetParameterResult) => void): Request<SSM.Types.GetParameterResult, AWSError>;
我的测试代码如下:
import {SSM} from 'aws-sdk'; import {SSMParameter} from './src/module/ssm/ssm-cache-parameter'; import {mocked} from 'jest-mock'; import { AWSError } from 'aws-sdk'; jest.mock('aws-sdk', () => { const mSSMInstance = { getParameter: jest.fn(), }; const mSSM = jest.fn(() => mSSMInstance); return { SSM: mSSM }; }); describe('amazon service mock', () => { let ssmParameter: SSMParameter; it('should get Parameter from Parameter Store', async () => { ssmParameter = SSMParameter.getInstance(); expect(SSM).toBeCalled(); console.log(ssmParameter); const mSSMInstance = new SSM(); const mData = { Parameter: { ARN: 'arn:aws:ssm:eu-test-1:123:NAME', LastModifiedDate: new Date(2020, 09, 1), Name: 'NAME', Type: 'String', Value: 'VALUE', Version: 1, } }; const error: AWSError = { code: 'ERROR_CODE', message: 'ERROR_MESSAGE', name: 'ERROR_NAME', time: new Date(2020, 09, 1), } mocked(mSSMInstance.getParameter).mockImplementationOnce( (params: SSM.GetParameterRequest, callback?: (err: AWSError, data: SSM.GetParameterResult) => void): any => { if (callback) { callback(error, mData); } }, ); const actual = ssmParameter.cache({Name: 'NAME', WithDecryption: false}, 30000); expect(actual).toBe('VALUE'); }); });
运行测试时遇到以下错误,原因是TypeScript无法匹配getParameter的重载定义:
Argument of type '(params: SSM.GetParameterRequest, callback?: ((err: AWSError, data: SSM.GetParameterResult) => void) | undefined) => any' is not assignable to parameter of type '{ (params: GetParameterRequest, callback?: ((err: AWSError, data: GetParameterResult) => void) | undefined): Request<GetParameterResult, AWSError>; (callback?: ((err: AWSError, data: GetParameterResult) => void) | undefined): Request<...>; }'.
Types of parameters 'params' and 'callback' are incompatible.
Type '((err: AWSError, data: GetParameterResult) => void) | undefined' is not assignable to type 'GetParameterRequest'.
Type 'undefined' is not assignable to type 'GetParameterRequest'.
解决方案
问题出在getParameter有两个重载签名,你的mock实现只匹配了第一个(带params+可选callback),但没有兼容第二个(直接带可选callback)。需要调整mock函数的参数处理逻辑,同时还要返回符合类型要求的Request对象:
修改后的mock实现代码:
mocked(mSSMInstance.getParameter).mockImplementationOnce( (param1?: SSM.GetParameterRequest | ((err: AWSError, data: SSM.GetParameterResult) => void), param2?: (err: AWSError, data: SSM.GetParameterResult) => void): any => { // 确定回调函数:第一个参数是函数则直接用,否则取第二个参数 let callback: ((err: AWSError, data: SSM.GetParameterResult) => void) | undefined; if (typeof param1 === 'function') { callback = param1; } else { callback = param2; } // 执行回调 if (callback) { callback(error, mData); } // 返回空的Request对象以满足类型约束(原方法无论是否传回调都会返回Request) return { promise: jest.fn() } as unknown as Request<SSM.Types.GetParameterResult, AWSError>; }, );
另外,如果ssmParameter.cache是异步函数,测试中需要使用await并调整断言方式:
const actual = await ssmParameter.cache({Name: 'NAME', WithDecryption: false}, 30000); expect(actual).toBe('VALUE');
这样就能解决TypeScript的类型不兼容问题,同时正确模拟getParameter的两种调用方式。
内容的提问来源于stack exchange,提问作者gab

