proxyrequire で外部ライブラリをモック化してテストする
Mocha・Chai・Sinon を使ってユニットテストを書いている時、require()
で読み込んだ外部ライブラリをモック化する必要が出た。
具体的にいうと、aws-sdk
パッケージを使った Lambda 関数を UT するために、aws-sdk
が実際には通信を行わないよう、モック化する必要があった。
コード中で require()
しているパッケージをモック化するには、proxyrequire
というパッケージを使うと簡単だった。
まず、テスト対象のコードはこんな感じ。
my-function.js
- AWS SDK を使い、SSM Parameter Store から SecureString を取得するだけ
const awsSdk = require('aws-sdk');
const ssm = new awsSdk.SSM();
exports.handler = async (event, context) => {
const secret = await ssm.getParameter({
Name: 'my-param',
WithDecryption: true
}).promise();
const secureString = secret.Parameter.Value;
context.succeed({
'My Secure String': secureString
});
};
続いて、テストコードはこんな感じ。
const chai = require('chai');
const proxyquire = require('proxyquire');
const sinon = require('sinon');
const expect = chai.expect;
describe('ユニットテスト', () => {
let myFunction = null;
let event = null;
let context = null;
let mockSsm = null;
let stubGetParameter = null;
beforeEach(() => {
event = {}; // 今回は適当に…
context = {
succeed: (result) => Promise.resolve(result) // Lambda 関数終了時の値を Promise で返すようにしておく
};
// モッククラスを作っておく。処理は書かなくて良いが関数定義が必要
mockSsm = class {
getParameter(_params) { }
};
// ProxyRequire を使う
myFunction = proxyquire('../src/my-function.js', {
'aws-sdk': { // require() で指定している文字列をそのまま指定する
SSM: mockSsm // モッククラスを注入する
}
});
});
it('正常系確認', (done) => {
const testSecureString = 'Test Value';
// モッククラスの関数を指定してフェイク処理を定義する
// 戻り値とかはモック化対象のライブラリの仕様に合わせて頑張って組み立てる
stubGetParameter = sinon.stub(mockSsm.prototype, 'getParameter').callsFake((params) => {
expect(params).to.deep.equal({ Name: 'my-param', WithDecryption: true });
return {
promise: () => {
return Promise.resolve({
Parameter: {
Value: testSecureString
}
});
}
};
});
myFunction.handler(event, context) // 関数を実行する
.then((result) => {
expect(stubGetParameter.called).to.be.true;
expect(result).to.deep.equal({
'My Secure String': testSecureString
});
done();
})
.catch((error) => {
done(error);
});
});
});
こんな感じ。
ProxyRequire の使用箇所は以下。
myFunction = proxyquire('../src/my-function.js', {
'aws-sdk': {
SSM: mockSsm
}
});
内部実装を見てみると、require()
部分の実装を prototype
から書き換えて、モック化した関数を注入しているようだ。
というか require()
の実装が require('module')
で拾えるの知らなかった…。
- 参考 : Modules: CommonJS modules | Node.js v15.5.0 Documentation
-
The module system is implemented in the
require('module')
module.
-
非同期関数と done()
を組み合わせる際、it()
に async
が使えなかったので、expect()
と done()
での検証部分を普通の Promise で書いている。
こねこねするのがなかなか大変だったが、proxyrequire でちゃんとモックが注入できたのでよきよき。