1

所以,我有一个连接到Redis的群集实例(使用ioredis)来存储数据的一些AWS拉姆达代码。我们希望在lambda容器中实例化集群以便重用,因为我们打算让这个lambda达到足够频繁的程度,以便从容器重用中获得性能优势。如何测试AWS lambda表达式

我们已经编写延伸Redis的集群来提供额外的检查和功能,因为我们在各种不同的lambda表达式使用这种包装类(称为RedisCluster)。

在过去,我已经能够在测试时剔除Redis集群实例,但是当我在容器中实例化它时,似乎在加载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集群。这是丑陋的,方法是只用于测试...所以我想必须有一个更好的方式来做到这一点。

这里是我已经添加到拉姆达嘲笑集群的方法。不幸的是,最初的集群仍然得到了纺加载拉姆达代码的时候,所以我得到的连接错误乱扔我的测试输出,虽然这工作时,我注入间谍或存根。我不喜欢这样,因为它会产生代码气味,并且只是为了满足测试而添加的方法。

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:打桩使用兴农

不会起作用,因为JavaScript对象并不是真正的类 '类'。我似乎无法将构造函数,只有方法,所以群集仍然最初在构造函数中旋转。

2:重新布线进口

似乎并不会因为事情与在测试执行顺序的工作。加载lambda代码后,RedisCluster立即启动。因此,在测试实际运行时,群集已经存在,并且不会使用重新导入的导入。

3:“依赖注入”

作品,但丑陋,很可能不会通过公关流程...

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

这就是我现在想,我还没有说完的代码知道它是否将工作或没有。

我在正确的轨道上?...在​​Java中,这很简单,但我不能为我的生活弄清楚在节点上干什么来模拟这种依赖关系。

+0

你看过SAM吗?亚马逊最近发布了这个本地测试AWS无服务器技术https://github.com/awslabs/aws-sam-local – Derek

+0

@Derek否我没有。乍一看,我认为这不会帮助解决我目前的问题。他们在README都表示,“你可以生成下列服务的模拟/样本事件的有效载荷: S3 室壁运动 DynamoDB CloudWatch的调度事件 Cloudtrail API网关” 我没有看到关于不幸的是嘲讽Elasticache什么...虽然SAM可能对测试lambda连接/调用其他AWS技术很有用,所以我很可能有机会在另一个项目中使用它。 –

+0

我有一个解决方案,但我使用'jest'而不是'chai'和'sinon'。 – dashmug

回答

0

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

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

然后在我的测试文件中,我加入了lambda源代码之前的语句​​,以避免Redis集群试图连接到不存在的集群实例。然后像往常一样使用sinon将包装类的方法剔除出去。

我希望这可以帮助别人。它不会传递linter,因为eslint要求导入语句位于顶部。所以我把这个声明转移到另一个js文件,并且在我包含源文件之前将它包含在内。

有点不好意思,但它有效。如果有更好的方法请别人加入。