如何用Jest与TypeScriptMock Twilio,避免单元测试调用真实API
用Jest Mock Twilio服务实现单元测试(避免调用真实API)
问题背景
现有两个AWS Lambda相关函数:sendSms(负责通过Twilio发送短信)和lambdaHandler(Lambda入口,调用密钥获取和短信发送逻辑)。运行单元测试时,代码会直接调用Twilio真实API,触发认证错误,需要通过Mock完全隔离外部依赖,避免真实请求。
解决方案
1. Mock Twilio客户端(针对sendSms函数测试)
通过Jest的jest.mock模拟整个Twilio模块,替换真实的客户端和消息创建方法,确保不会发起真实网络请求。
测试文件示例(sendSms.test.ts)
import { sendSms } from './sendSms'; import { SQSEvent } from 'aws-lambda'; import Twilio from 'twilio'; // Mock Twilio模块 jest.mock('twilio', () => { // 模拟messages.create方法 const mockMessagesCreate = jest.fn(); return jest.fn().mockImplementation(() => ({ messages: { create: mockMessagesCreate } })); }); describe('sendSms', () => { const mockSecrets = { SecretString: JSON.stringify({ TWILIO_ACCOUNT_SID: 'mock-account-sid', TWILIO_AUTH_TOKEN: 'mock-auth-token', TWILIO_PHONE_NUMBER: '+1234567890' }) }; const mockSqsEvent: SQSEvent = { Records: [ { body: JSON.stringify({ number: '9876543210', message: 'Test message' }), messageId: 'mock-message-id', receiptHandle: 'mock-receipt-handle', attributes: {}, messageAttributes: {}, md5OfBody: 'mock-md5', eventSource: 'aws:sqs', eventSourceARN: 'mock-arn', awsRegion: 'us-east-1' } ] }; beforeEach(() => { // 重置Mock调用记录 (Twilio as jest.Mock).mockClear(); (Twilio as jest.Mock).mockImplementation(() => ({ messages: { create: jest.fn() } })); }); test('成功发送短信', async () => { // 模拟create方法返回成功结果 const mockMessageResult = { sid: 'mock-message-sid' }; (Twilio as jest.Mock).mockImplementation(() => ({ messages: { create: jest.fn().mockResolvedValue(mockMessageResult) } })); const result = await sendSms(mockSqsEvent, mockSecrets); expect(result).toEqual(mockMessageResult); // 验证Twilio客户端是否正确初始化 expect(Twilio).toHaveBeenCalledWith('mock-account-sid', 'mock-auth-token'); // 验证消息创建参数是否正确 expect((Twilio as jest.Mock).mock.results[0].value.messages.create).toHaveBeenCalledWith({ from: '+1234567890', to: '+9876543210', body: 'Test message' }); }); test('缺少Twilio配置时返回错误提示', async () => { const invalidSecrets = { SecretString: JSON.stringify({ TWILIO_ACCOUNT_SID: '', TWILIO_AUTH_TOKEN: 'mock-auth-token', TWILIO_PHONE_NUMBER: '+1234567890' }) }; const result = await sendSms(mockSqsEvent, invalidSecrets); expect(result).toBe('You are missing one of the variables you need to send a message'); // 验证Twilio客户端未被初始化 expect(Twilio).not.toHaveBeenCalled(); }); test('短信发送失败时返回错误信息', async () => { // 模拟create方法抛出错误 const mockError = { errorCode: 123, errorMessage: 'lorem-ipsum' }; (Twilio as jest.Mock).mockImplementation(() => ({ messages: { create: jest.fn().mockRejectedValue(mockError) } })); const result = await sendSms(mockSqsEvent, mockSecrets); expect(result).toBe(`Failed to send sms message. Error Code: 123 / Error Message: lorem-ipsum`); }); });
2. Mock依赖函数(针对lambdaHandler测试)
Lambda入口函数依赖getSecret和sendSms,直接Mock这两个函数即可,无需关心Twilio的内部实现。
测试文件示例(lambdaHandler.test.ts)
import { lambdaHandler } from './lambdaHandler'; import { SQSEvent } from 'aws-lambda'; import { getSecret } from './services/obtainSecrets/getSecret'; import { sendSms } from './services/sendSms/sendSms'; // Mock依赖函数 jest.mock('./services/obtainSecrets/getSecret', () => ({ getSecret: jest.fn() })); jest.mock('./services/sendSms/sendSms', () => ({ sendSms: jest.fn() })); describe('lambdaHandler', () => { const mockSqsEvent: SQSEvent = { Records: [ { body: JSON.stringify({ number: '9876543210', message: 'Test' }), messageId: 'mock-id', receiptHandle: 'mock-handle', attributes: {}, messageAttributes: {}, md5OfBody: 'mock-md5', eventSource: 'aws:sqs', eventSourceARN: 'mock-arn', awsRegion: 'us-east-1' } ] }; beforeEach(() => { jest.clearAllMocks(); }); test('成功处理请求', async () => { const mockSecret = { SecretString: JSON.stringify({ TWILIO_ACCOUNT_SID: 'mock-sid', TWILIO_AUTH_TOKEN: 'mock-token', TWILIO_PHONE_NUMBER: '+123456789' })}; const mockSmsResponse = { sid: 'mock-message-sid' }; (getSecret as jest.Mock).mockResolvedValue(mockSecret); (sendSms as jest.Mock).mockResolvedValue(mockSmsResponse); const result = await lambdaHandler(mockSqsEvent); expect(result.code).toBe(200); expect(getSecret).toHaveBeenCalled(); expect(sendSms).toHaveBeenCalledWith(mockSqsEvent, mockSecret); }); test('获取密钥失败时返回错误', async () => { const mockError = new Error('Failed to get secret'); (getSecret as jest.Mock).mockRejectedValue(mockError); await expect(lambdaHandler(mockSqsEvent)).rejects.toThrow(mockError); }); });
额外注意事项
- 原
sendSms函数中,client.messages.create是异步操作,但代码未使用await,会直接返回Promise而非实际结果,建议添加await确保逻辑正确:const message = await client.messages.create({ from: twilioNumber, to: userNumber, body: body.message, }); - 原
sendSms的循环逻辑中,处理第一个record后就直接return,后续消息不会被处理,若需要批量发送需调整逻辑。
内容的提问来源于stack exchange,提问作者Fran Herrera
相关产品推荐
相关产品推荐

