You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 08:15:36