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文件,並且在我包含源文件之前將它包含在內。

有點不好意思,但它有效。如果有更好的方法請別人加入。