如何为正在访问 aws 资源的函数编写单元测试?

How to write unit test for the function which is accessing aws resources?

我有一个函数正在访问多个 aws 资源,现在需要测试这个函数,但我不知道如何模拟这些资源。

我已经尝试关注 github 的 aws-sdk-mock,但没有得到太多。

function someData(event, configuration, callback) {

    // sts set-up
    var sts = new AWS.STS(configuration.STS_CONFIG);

    sts.assumeRole({
      DurationSeconds: 3600,
      RoleArn: process.env.CROSS_ACCOUNT_ROLE,
      RoleSessionName: configuration.ROLE_NAME
    }, function(err, data) {
      if (err) {
        // an error occurred
        console.log(err, err.stack);
      } else {
        // successful response

        // resolving static credential
        var creds = new AWS.Credentials({
          accessKeyId: data.Credentials.AccessKeyId,
          secretAccessKey: data.Credentials.SecretAccessKey,
          sessionToken: data.Credentials.SessionToken
        });

         // Query function
         var dynamodb = new AWS.DynamoDB({apiVersion: configuration.API_VERSION, credentials:  creds, region: configuration.REGION});
         var docClient = new AWS.DynamoDB.DocumentClient({apiVersion: configuration.API_VERSION, region: configuration.REGION, endpoint: configuration.DDB_ENDPOINT, service: dynamodb });

            // extract params
            var ID = event.queryStringParameters.Id;
            console.log('metrics of id ' + ID);

            var params = {
                TableName: configuration.TABLE_NAME,
                ProjectionExpression: configuration.PROJECTION_ATTR,
                KeyConditionExpression: '#ID = :ID',
                ExpressionAttributeNames: {
                    '#ID': configuration.ID
                },
                ExpressionAttributeValues: {
                    ':ID': ID
                }
            };

            queryDynamoDB(params, docClient).then((response) => {
                console.log('Params: ' + JSON.stringify(params));
                // if the query is Successful
                if( typeof(response[0]) !== 'undefined'){
                    response[0]['Steps'] = process.env.STEPS;
                    response[0]['PageName'] = process.env.STEPS_NAME;
                }
                console.log('The response you get', response);
                var success = {
                    statusCode: HTTP_RESPONSE_CONSTANTS.SUCCESS.statusCode,
                    body: JSON.stringify(response),
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    isBase64Encoded: false
                };
                return callback(null, success);
            }, (err) => {
                // return internal server error
                return callback(null, HTTP_RESPONSE_CONSTANTS.BAD_REQUEST);
            });
      }

    });

}

这是我需要测试的 lambda 函数,这里还使用了一些环境变量。

现在我尝试使用 aws-sdk-mock 为上述函数编写单元测试,但我仍然无法弄清楚如何实际执行它。任何帮助将不胜感激。下面是我的测试代码

describe('test getMetrics', function() {

    var expectedOnInvalid = HTTP_RESPONSE_CONSTANTS.BAD_REQUEST;

    it('should assume role ', function(done){
        var event = {
          queryStringParameters : {
              Id: '123456'
          }
        };

        AWS.mock('STS', 'assumeRole', 'roleAssumed');
        AWS.restore('STS');
        AWS.mock('Credentials', 'credentials');
        AWS.restore('Credentials');
        AWS.mock('DynamoDB.DocumentClient', 'get', 'message');
        AWS.mock('DynamoDB', 'describeTable', 'message');
        AWS.restore('DynamoDB');
        AWS.restore('DynamoDB.DocumentClient');

        someData(event, configuration, (err, response) => {
            expect(response).to.deep.equal(expectedOnInvalid);
            done();
        });


    });


});

我收到以下错误:

{ MultipleValidationErrors: There were 2 validation errors:
* MissingRequiredParameter: Missing required key 'RoleArn' in params
* MissingRequiredParameter: Missing required key 'RoleSessionName' in params

我将单元测试视为检查您的域(业务)规则是否得到满足的一种方式。

就您的 Lambda 仅包含 AWS 服务的集成而言,为其编写单元测试没有多大意义。

模拟所有资源意味着,您的测试将仅测试这些模拟之间的通信 - 这样的测试没有价值。

外部资源是指input/output,这是集成测试的重点。

编写集成测试并运行它们作为针对实际部署资源的集成管道的一部分。

我非常不同意@ttulka 的回答,所以我决定也添加我自己的回答。

鉴于您在 Lambda 函数中收到了一个事件,您很可能会处理该事件,然后调用其他服务。它可能是对 S3、DynamoDB、SQS、SNS、Kinesis 的调用……应有尽有。在这一点上有什么要断言的?

论据正确!

考虑以下事件:

{
   "data": "some-data",
   "user": "some-user",
   "additionalInfo": "additionalInfo"
}

现在假设您想要调用 documentClient.put 并且您想要确保您传递的参数是正确的。假设您不希望保留 additionalInfo 属性,因此,在您的代码中的某处,您可以用它来摆脱这个属性

delete event.additionalInfo

对吗?

您现在可以创建一个单元测试来 断言 正确的参数已传递到 documentClient.put,这意味着最终对象应该如下所示:

 {
   "data": "some-data",
   "user": "some-user"
 }

您的测试必须断言 documentClient.put 是通过 JSON 调用的,深度等于上面的 JSON。

如果您或任何其他开发人员现在出于某种原因删除了 delete event.additionalInfo 行,测试将开始失败。

而且这个很厉害!如果您确保您的代码按您期望的方式工作,您基本上根本不必担心创建集成测试。

现在,如果 SQS 消费者 Lambda 期望消息正文包含某些字段,生产者 Lambda 应始终处理它以确保在队列中保留正确的参数。我想现在你明白了吧?

我总是告诉我的同事们,如果我们可以创建 适当的 单元测试,我们应该可以顺利进行 95% 的测试案例,将集成测试排除在外。当然,最好同时拥有两者,但考虑到在创建集成测试(如设置环境、凭据,有时甚至是不同的帐户)上所花费的时间,是不值得的。但那只是我的个人意见。非常欢迎您和@ttulka 提出异议。

现在,回到你的问题:

您可以使用 Sinon to mock and assert arguments in your Lambda functions. If you need to mock a 3rd-party service (like DynamoDB, SQS, etc), you can create a mock object and replace it in your file under test using Rewire。这通常是我骑的路,到目前为止它一直很棒。

尝试显式设置 aws-sdk 模块。
在顶层 node_modules 项目文件夹中不包含 aws-sdk 的项目结构将无法正确模拟。这方面的一个例子是在嵌套项目目录中安装 aws-sdk。您可以通过使用 setSDK().

显式设置嵌套 aws-sdk 模块的路径来解决这个问题
const AWSMock = require('aws-sdk-mock');
import AWS = require('aws-sdk');
AWSMock.setSDKInstance(AWS);

关于这方面的更多细节:Read aws-sdk-mock documentation,他们已经解释得更好了。