因此,我有一些AWS lambda代码连接到Redis Cluster实例(使用ioredis)来存储数据。我们希望在lambda容器中实例化Cluster以供重用,因为我们打算经常使这个lambda命中,以便我们从容器重用中获得性能优势。
我们编写了一个包装类(称为RedisCluster),它扩展了Redis群集以提供额外的检查和功能,因为我们在各种不同的lambda中使用它。
过去,我在测试时已经能够存根Redis Cluster实例,但是当我在容器中实例化它时,看起来在加载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;
我无法正确地注入任何依赖项(这是一个lambda),并且对Redis集群进行存根似乎不起作用,因为它在加载源代码时被实例化。但是,我可以在测试之前通过添加导出的函数来替换Redis Cluster。这很丑陋,而且这个方法只用 进行测试......所以我想有必要有更好的方法来做到这一点。
这是我添加到lambda以模拟群集的方法。不幸的是,当加载lambda代码时,初始Cluster仍然会被启动,所以我得到连接错误乱丢我的测试输出,虽然这在注入间谍或存根时确实有效。我不喜欢这个,因为它会产生代码气味,并且只是为了满足测试而添加的方法。
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
因为javascript对象不是真正的类而无法工作。我似乎无法删除构造函数,只有方法,因此集群仍然最初在构造函数中生成。
2:重新连接导入
由于测试中执行事物的顺序,似乎无法工作。加载lambda代码后,RedisCluster会立即旋转。因此,当测试实际运行时,群集已经存在,并且不会使用重新连接的导入。
3:'依赖注入'
有效,但很丑陋,可能无法通过公关过程......
4:重写包装器类以等待连接,直到执行第一个命令
这就是我现在正在尝试的,我还没有完成代码,知道它是否会起作用。
我是否走在正确的轨道上?...在Java中,这很简单,但我不能为我的生活找出在节点中做什么来干净地模拟这种依赖。
答案 0 :(得分:0)
我找到了解决我的问题的方法,虽然它并不理想,但它确实解决了我之前提出的模拟依赖注入解决方案中的缺点。
我只是将我的包装RedisCluster类的实例更改为使用process.env.opts
而不是{ host: process.env.HOST, port: process.env.PORT }
进行实例化,其中opts只是上面使用的地图。
然后在我的测试文件中,我在加载lambda源代码之前包含了语句process.env.opts = { mock: "stuff" }
,以避免Redis群集尝试连接到不存在的群集实例。然后我像往常一样使用sinon将方法存根到包装类中。
我希望这有助于某人。它不会传递一个linter,因为eslint要求import语句位于顶部。所以我将该语句移动到另一个js文件,并在我包含源文件之前将其包含在内。
有点hacky,但它确实有效。如果有更好的方式,请有人加入。