javascript - 如何测试 AWS lambda

标签 javascript amazon-web-services ecmascript-6 aws-lambda amazon-elasticache

因此,我有一些连接到 Redis 集群实例(使用 ioredis)来存储数据的 AWS lambda 代码。我们希望在 lambda 容器中实例化 Cluster 以便重用,因为我们打算让这个 lambda 足够频繁地命中,以便我们可以从容器重用中获得性能优势。

我们编写了一个包装类(称为 RedisCluster),它扩展了 Redis Cluster 以提供额外的检查和功能,因为我们在各种不同的 lambda 中使用它。

过去,我能够在测试时删除 Redis 集群实例,但是当我在容器中实例化它时,集群似乎在加载 lambda 源代码时(即在测试执行之前)启动了。当我的测试运行时,我收到一条错误,指示集群无法连接到节点实例。

lambda

let redisCluster = new RedisCluster([{ host: process.env.HOST, port: process.env.PORT }]);

function isKeyInCache(cacheKey) {
    logger.info(`Searching for key::${cacheKey}`);
    return redisCluster.get(cacheKey);
}

exports.handler = baseHandler((e, ctx, cb) => {
    ctx.callbackWaitsForEmptyEventLoop = false;
    const cacheKey = `${key}`;
    isKeyInCache(cacheKey).then((response) => {
        if (response) {
            logger.info('Key is registered');
            redisCluster.removeKey(cacheKey).then(() => {
                const result = { status: 'Registered' };
                cb(null, result);
            }).catch((err) => {
                logger.error(err);
                cb(err, 'Error');
            });
        } else {
            const result = { status: 'NotFound' };
            cb(null, result);
        }
    });

    redisCluster.on('error', () => {
        cb('An error has occurred with the redis cluster');
    });

这是包装类

class RedisCluster extends Redis.Cluster {
    constructor(opts) {
        super(opts);
    }

    removeKey(cacheKey) {
        return new Promise((resolve, reject) => {
            super.del(cacheKey, (err, reply) => {
                if (err) {
                    logger.error(`Failed to remove key::${cacheKey} Error response: ${err}`);
                    reject(err);
                } else {
                    logger.info(`Successfully removed key::${cacheKey} response: ${reply}`);
                    resolve();
                }
            });
        });
    }

    quitRedisCluster() {
        return new Promise((resolve, reject) => {
            if (this) {
                super.quit(() => {
                    logger.info('Quitting redis cluster connection...');
                    resolve();
                }).catch((err) => {
                    logger.error('Error closing cluster');
                    reject(err);
                });
            } else {
                logger.error('Cluster not defined');
                reject();
            }
        });
    }
}
module.exports = RedisCluster;

我无法正确注入(inject)任何依赖项(这是一个 lambda),并且 stub Redis 集群似乎不起作用,因为它是在加载源代码时实例化的。不过,我可以在测试之前通过添加导出函数来替换 Redis 集群。这很丑陋,而且该方法用于测试......所以我想必须有更好的方法来做到这一点。

这是我添加到 lambda 来模拟集群的方法。不幸的是,当加载 lambda 代码时,初始集群仍然会旋转,因此我的测试输出中出现了连接错误,尽管这在我注入(inject) spy 或 stub 时确实有效。我不喜欢这个,因为它会产生代码味道,并且是仅为满足测试而添加的方法。

exports.injectCluster = (injectedDependency) => {
    redisCluster.disconnect();
    redisCluster = injectedDependency;
};

我的测试看起来像这样。

import Promise from 'bluebird';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import Context from 'mock-ctx';
import sinon from 'sinon';
import { handler, injectCluster } from '../src';

chai.use(chaiAsPromised);

let redisClusterConstructor;
let removeKey;
let on;
let disconnect;
let get;
const ctx = new Context();

describe('lambda', () => {
    beforeEach(() => {
        removeKey = sinon.spy(() => Promise.resolve({}));
        on = sinon.spy((e, cb) => {});
        disconnect = sinon.spy(() => {});

        redisClusterConstructor = {
            removeKey,
            on,
            disconnect
         };
    });

    it('should get key in redis if key exist', () => {
        get = sinon.spy((k) => Promise.resolve('true'));
        redisClusterConstructor['get'] = get;
        injectCluster(redisClusterConstructor);
        const promise = Promise.fromCallback(cb => handler(e, ctx, cb));
        return chai.expect(promise).to.be.fulfilled.then((response) => {
            chai.assert.isTrue(get.calledOnce);
            chai.assert.isTrue(removeKey.calledOnce);
            chai.expect(response).to.deep.equal({ status: 'Registered' });
        });
    });
});

我尝试过的事情:

1:使用 sinon 对“类”进行 stub

不会工作,因为 javascript 对象并不是真正的类。我似乎无法删除构造函数,只能删除方法,因此集群最初仍会在构造函数中启动。

2:重新连接导入

由于测试中执行的顺序,似乎不起作用。加载 lambda 代码后,RedisCluster 立即启动。因此,当测试实际运行时,集群已经存在,并且不会使用重新连接的导入。

3:“依赖注入(inject)”

可以,但是很丑,而且可能无法通过 PR 流程...

4:重写包装类以等待连接,直到执行第一个命令

这就是我现在正在尝试的,我还没有完成代码,不知道它是否有效。

我走在正确的轨道上吗?...在​​ Java 中,这很简单,但我一生都无法弄清楚在 Node 中该怎么做才能干净地模拟这种依赖关系。

最佳答案

我找到了解决问题的方法,尽管它并不理想,但它确实解决了我之前提出的模拟依赖注入(inject)解决方案中的缺点。

我只是将包装器 RedisCluster 类的实例更改为使用 process.env.opts 进行实例化而不是{ host: process.env.HOST, port: process.env.PORT } ,其中 opts 只是上面使用的映射。

然后在我的测试文件中我包含了语句 process.env.opts = { mock: "stuff" }在加载 lambda 源代码之前,以避免 Redis 集群尝试连接到不存在的集群实例。然后我像往常一样使用 sinon 删除了包装类的方法。

我希望这对某人有帮助。它不会按原样传递 linter,因为 eslint 要求 import 语句位于顶部。因此,我将该语句移至另一个 js 文件,并在包含源文件之前包含它。

有点hacky,但它有效。如果有更好的方法请大家指正。

关于javascript - 如何测试 AWS lambda,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46371003/

相关文章:

javascript - 无法从 api Controller js 文件获取数据以使用 Vue.js 应用程序在 sails js 中查看 ejs 文件

javascript - 在javascript if语句中有没有更好的方法来处理 'do nothing'?

javascript - nginx 拒绝访问 JS 和 CSS

node.js - 无法从任何提供商 Nodemailer aws 加载凭证

node.js - 哪种方法更好?从客户端还是从服务器端上传文件?

javascript - 为什么 webpack 在需要输出时返回一个空对象?

javascript - Safari 中的 onmouseover 问题

javascript - Android WebView - 使用 Javascript 设置 HTML 字段焦点

amazon-web-services - 如何让 AWS ECS 自动将我的容器的端口映射到主机 (EC2)

javascript - $ (":not(selector)")在普通/ Vanilla javascript中?