我怎样才能开玩笑地模拟 aws-sdk?

How can I mock aws-sdk with jest?

我想开玩笑地模拟 aws-sdk。其实我只关心一个功能。我怎样才能做到这一点?我已经阅读了关于开玩笑地模拟 类 的文档,但是文档很复杂,我不太理解它们。

这是我最好的尝试:

handler.test.js

'use strict';

const aws = require('aws-sdk');
const { handler } = require('../../src/rotateSecret/index');

jest.mock('aws-sdk');

const event = {
  SecretId: 'test',
  ClientRequestToken: 'ccc',
  Step: 'createSecret',
};

describe('rotateSecret', () => {
  it.only('should not get or put a secret', async () => {
    aws.SecretsManager.mockImplementation(() => ({
      getSecretValue: () => ({}),
    }));
    expect.assertions(1);

    await handler(event);

    // You can see what I am trying to do here but it doesn't work
    expect(aws.SecretsManager.getSecretManager).not.toHaveBeenCalled();
  });
});

handler.js

exports.handler = async (event) => {
  const secretsManager = new aws.SecretsManager();
  const secret = await secretsManager.describeSecret({ SecretId: event.SecretId }).promise();

  if (someCondition) {
    console.log("All conditions not met");
    return;
  }

  return secretsManager.getSecretValue(someParams)
};

好的,我的处理方式如下:

AWS-SDK 模拟


aws-sdk 创建一个实际的 mock 并将其放入项目根目录下的 __mocks__/aws-sdk.js 文件中

// __mocks__/aws-sdk.js

class AWS {
  static SecretsManager = class {
    describeSecret = jest.fn(() =>{
       return { promise: ()=> Promise.resolve({ ARN: "custom-arn1", Name: "describeSec" })}
    });
    getSecretValue = jest.fn(() =>{
       return {promise: ()=> Promise.resolve({ ARN: "custom-arn2", Name: "getSecretVal" })
    });
  };
}

module.exports = AWS;

我在 SecretsManager 之前使用过 static 因为 AWS class 从未实例化但它想要访问 SecretsManager class.

SecretsManager 中,我定义了 2 个函数并使用 jest.fn.

对它们进行了存根

现在与您在测试文件中所做的相同:

jest.mock('aws-sdk');

如何测试


To test if your mock functions are called, thats the tricky part (so i will detail that at the end of this post).

更好的方法是在所有处理完成后对主函数的最终结果进行断言。

断言


回到你的测试文件,我会简单地用 await 调用处理程序(就像你已经拥有的那样),然后像这样对最终结果进行断言:

// test.js

describe("rotateSecret", () => {
  it.only("should not get or put a secret", async () => {
    const event = {name:"event"};
    const result = await handler(event);
    expect(result).toEqual("whatever-your-function-is-expected-to-return");
  });
});

正在测试 Secret Manager 的函数调用


为此,您需要调整主 handler.js 文件本身,并且需要从主函数体中取出 invocation of secrets Manager,如下所示:

const secretsManager = new aws.SecretsManager(); // <---- Declare it in outer scope

exports.handler = async (event) => {
  const secret = await secretsManager
    .describeSecret({ SecretId: event.SecretId })
    .promise();

  if (someCondition) {
    console.log("All conditions not met");
    return;
  }

  return secretsManager.getSecretValue(someParams);
};

然后回到您的 test.js 文件,您需要在启动处理函数之前类似地声明 SecretsManager 调用,如下所示:

//test.js

describe("rotateSecret", () => {
  const secretsManager = new aws.SecretsManager(); // <---- Declare it in outer scope

  it.only("should not get or put a secret", async () => {
    const event = {name:"event"};

    await handler(event);

    // Now you can make assertions on function invocations
    expect(secretsManager.describeSecret).toHaveBeenCalled();

    // OR check if passed args were correct
    expect(secretsManager.describeSecret).toHaveBeenCalledWith({
      SecretId: event.SecretId,
    });
  });
});

这将允许您对函数调用以及传递的参数进行断言。

The reason I declare it outside function scope is to tell Jest that secretsManager should be existing somewhere in global scope and it should be used from there.

之前,我们在函数范围内声明了它,因此 Jest 会调用它,但我们无法访问它。

我们不能像这样直接引用它 AWS.SecretsManager.getSecretManager 因为 getSecretManager 方法只有在你实例化 SecretsManager class 之后才可用(即使你这样做了,您将获得 class 的新实例,这对任何断言都没有帮助。

__mocks__/aws.js 假模块的缺点


明显的问题是 - 您在每次调用时都对函数进行存根处理,也许您不希望这样。

也许您只想针对特定测试将其存根一次,但对于其余测试,您希望它 运行 正常。

在这种情况下,您不应创建 __mocks__ 文件夹。

相反,创建一个 one-time 伪造但确保您的 SecretsManager 调用像以前一样在测试文件的外部范围内。

//test.js
const aws = require("aws-sdk");

describe("rotateSecret", () => {
 // Declare it in outer scope
  const secretsManager = new aws.SecretsManager();

  it.only("should not get or put a secret", async () => {
    const event = {name:"event"};

    // Create a mock for this instance ONLY
    secretsManager.describeSecret = jest.fn().mockImplementationOnce(()=>Promise.resolve("fake-values"));

    await handler(event);
    expect(secretsManager.describeSecret).toHaveBeenCalled();
    expect(secretsManager.describeSecret).toHaveBeenCalledWith({
      SecretId: event.SecretId,
    });
  });
});