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

如何在Jest中监视嵌套的AWS Lambda.invoke及promise方法

如何正确监视AWS Lambda的.invoke和.promise函数进行测试

测试AWS Lambda函数时,希望验证lambda.invoke方法是否被传入正确参数,但测试始终提示该方法未被调用,怀疑监视方式有误。

待测试函数

/* Amplify Params - DO NOT EDIT
    ENV
    FUNCTION_EMAIL_NAME
  STRIPE_ENDPOINT_SECRET
    REGION
Amplify Params - DO NOT EDIT */
const stripe = require('stripe');
const AWS = require('aws-sdk');

/**
 * @type {import('@types/aws-lambda').APIGatewayProxyHandler}
 */
exports.handler = async (event) => {
  console.log(`EVENT: ${JSON.stringify(event)}`);

  const lambda = new AWS.Lambda({ region: process.env.REGION });

  const sig = event.headers['Stripe-Signature'];

  let stripeEvent;

  try {
    stripeEvent = stripe.webhooks.constructEvent(
      event.body,
      sig,
      process.env.STRIPE_ENDPOINT_SECRET
    );
  } catch (err) {
    return {
      statusCode: 400,
      headers: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Headers': '*',
      },
      body: JSON.stringify(`Webhook Error: ${err.message}`),
    };
  }

  // Handle the event
  switch (stripeEvent.type) {
    case 'payment_intent.succeeded':
      // eslint-disable-next-line no-case-declarations
      const paymentIntent = stripeEvent.data.object;
      // Then define and call a function to handle the stripeEvent payment_intent.succeeded
      try {
        const result = await lambda
          .invoke({
            FunctionName: process.env.FUNCTION_EMAIL_NAME,
            Payload: JSON.stringify({
              ...event,
              pathParameters: {
                userID: paymentIntent.metadata.userID,
              },
              body: JSON.stringify({
                user: {
                  emailAddress: paymentIntent.metadata.email,
                },
              }),
            }),
          })
          .promise();

        if (result.StatusCode === 200) {
          return {
            statusCode: 200,
            headers: {
              'Access-Control-Allow-Origin': '*',
              'Access-Control-Allow-Headers': '*',
            },
            body: JSON.stringify('Email successfully sent!'),
          };
        }
      } catch (err) {
        console.log('Email failed!', err);
      }
      break;
    // ... handle other stripeEvent types
    default:
      console.log(`Unhandled stripeEvent type ${stripeEvent.type}`);
  }

  return {
    statusCode: 200,
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Headers': '*',
    },
    body: JSON.stringify('Hello from Lambda!'),
  };
};

失败的测试代码

const AWS = require('aws-sdk');
const stripe = require('stripe');

const { handler } = require('./index');
const event = require('./event.json');

jest.mock('stripe');
jest.mock('aws-sdk');

describe('handler', () => {
  const { env } = process;

  beforeEach(() => {
    jest.resetAllMocks();
    jest.restoreAllMocks();
    jest.clearAllMocks();

    jest.resetModules();
    process.env = {
      ...env,
      REGION: 'us-east-1',
      FUNCTION_EMAIL_NAME: 'example_function_name',
      STRIPE_ENDPOINT_SECRET: 'stripe_test_key',
    };
  });

  afterEach(() => {
    process.env = env;
  });

  it('should call the email lambda if payment intent succeeded', async () => {
    const lambdaSpy = jest.spyOn(AWS, 'Lambda');
    const stripeSpy = jest.spyOn(stripe.webhooks, 'constructEvent');
    const test = jest.fn();

    stripeSpy.mockReturnValue({
      type: 'succeeded',
    });

    lambdaSpy.mockReturnValue({ invoke: test, promise: jest.fn() });

    await handler(event);

    expect(test).toHaveBeenCalled();
  });
});

问题分析与解决方案

问题1:Stripe事件类型不匹配

原代码触发invoke的条件是stripeEvent.type === 'payment_intent.succeeded',但测试中mock的返回值是{ type: 'succeeded' },类型不匹配导致无法进入目标分支,自然不会调用invoke。

问题2:Lambda实例的Mock方式错误

原代码通过new AWS.Lambda()创建实例后,调用invoke方法再链式调用promise()。测试中直接给Lambda构造函数返回{ invoke: test, promise: jest.fn() }的结构错误——promise是invoke返回对象的方法,不是实例的方法。

修正后的测试代码

const AWS = require('aws-sdk');
const stripe = require('stripe');

const { handler } = require('./index');
const event = require('./event.json');

jest.mock('stripe');
jest.mock('aws-sdk');

describe('handler', () => {
  const { env } = process;

  beforeEach(() => {
    jest.resetAllMocks();
    jest.clearAllMocks();

    process.env = {
      ...env,
      REGION: 'us-east-1',
      FUNCTION_EMAIL_NAME: 'example_function_name',
      STRIPE_ENDPOINT_SECRET: 'stripe_test_key',
    };

    // 正确mock Stripe事件,匹配目标类型并包含所需metadata
    stripe.webhooks.constructEvent.mockReturnValue({
      type: 'payment_intent.succeeded',
      data: {
        object: {
          metadata: {
            userID: 'test-user-id',
            email: 'test@example.com'
          }
        }
      }
    });

    // 模拟invoke方法返回带有promise的对象,promise resolve出正确结果
    const mockInvoke = jest.fn().mockReturnValue({
      promise: jest.fn().mockResolvedValue({ StatusCode: 200 })
    });
    // 让Lambda构造函数返回带有invoke方法的实例
    AWS.Lambda.mockImplementation(() => ({
      invoke: mockInvoke
    }));
  });

  afterEach(() => {
    process.env = env;
  });

  it('should call the email lambda if payment intent succeeded', async () => {
    // 获取mock的invoke方法
    const mockInvoke = AWS.Lambda.mock.results[0].value.invoke;

    await handler(event);

    // 验证invoke被调用
    expect(mockInvoke).toHaveBeenCalled();
    // 还可以验证传入的参数是否正确
    expect(mockInvoke).toHaveBeenCalledWith(expect.objectContaining({
      FunctionName: 'example_function_name',
      Payload: expect.any(String)
    }));
  });
});

关键修正点

  1. Stripe事件Mock:返回type: 'payment_intent.succeeded',并补充data.object.metadata结构,确保代码能读取到userID和email。
  2. Lambda实例Mock:
    • 用mockImplementation让AWS.Lambda构造函数返回包含invoke方法的实例。
    • invoke方法返回包含promise方法的对象,promise方法mockResolvedValue返回{ StatusCode: 200 },确保代码能进入成功分支。
  3. 验证逻辑:通过AWS.Lambda.mock.results[0].value.invoke获取mock的invoke方法,进行调用验证,还可进一步验证传入参数的细节。

内容的提问来源于stack exchange,提问作者klaurtar1

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 17:20:35