It's a peacehell world.

こんな名前ですが情報技術について主に扱うブログです。

[Node.js] sinon の stub を使う

Node.js のユニットテストが初めてで一苦労したのでまとめておきます。

目的

Node.js のユニットテストPython でいうところの

docs.python.org

をやりたい。

例えば AWS Lambda 関数で SSM および DynamoDB から値を取得する箇所があった場合、外部サービスとの結合なので実際に AWS にアクセスするのではなくモック化してテストを行いたいのです。

前提

  • AWS Lambda 関数が Node.js で実装されている
  • AWS Lambda 関数では aws-sdk を使って SSM および DynamoDB にアクセスしている

環境

  • Lambda ランライム Node.js 14.x
  • aws-sdk 2.1215.0
  • mocha 10.0.0
  • power-assert 1.6.1
  • proxyquire 2.1.3
  • sinon 14.0.0

実装

Lambda

const aws = require('aws-sdk')
const docClient = new aws.DynamoDB.DocumentClient()

exports.handler = async (event) => {
  // SSM
  const { Parameters } = await (new aws.SSM())
    .getParameters({
      Names: ["SECRET_KEY_1", "SECRET_KEY_2"],
      WithDecryption: true,
    })
    .promise()
  // DynamoDB
  const params = {
    TableName: process.env.TABLE_NAME,
    Key: {
      id: event.arguments.id
    }
  }
  const result = await docClient.get(params).promise()

  // 処理

  return {}
}

テストコード

'use strict'
const assert = require('power-assert')
const sinon = require('sinon')
const proxyquire = require('proxyquire')

describe('Test', () => {
  let lambda
  let proxyDynamoDB
  let proxySSM
  describe('#handler()', () => {
    beforeEach(() => {
      // hide lambda log
      sinon.stub(console, 'log').returns()
      sinon.stub(console, 'error').returns()
      // stub aws
      proxyDynamoDB = class { batchGet() { } get() { } query() { } put() { } delete() { } }
      proxySSM = class { getParameters() { } }
      // inject
      lambda = proxyquire('../index.js', {
        'aws-sdk': {
          DynamoDB: { DocumentClient: proxyDynamoDB },
          SSM: proxySSM,
        }
      })
      // mock SSM getParameters
      sinon.stub(proxySSM.prototype, 'getParameters').returns({
        promise: () => Promise.resolve({
          Parameters: [
            {
              Name: 'KEY_1',
              Value: 'DUMMY_VALUE_1',
            },
            {
              Name: 'KEY_2',
              Value: 'DUMMY_VALUE_2',
            }
          ]
        })
      })
      process.env.TABLE_NAME = ''
      sinon.stub(process.env, 'TABLE_NAME').value('DUMMY_TABLE_NAME')
      const dynamoDbStub = sinon.stub(proxyDynamoDB.prototype, 'get')
      // mock DynamoDB get
      dynamoDbStub.withArgs({
        TableName: process.env.TABLE_NAME,
        Key: {
          id: 'DUMMY_ID'
        }
      }).returns({
        promise: () => Promise.resolve({
          Item: {
            name: 'DUMMY_NAME',
            email: 'DUMMY_EMAIL',
          }
        })
      })
    })
    it('テスト1', async () => {
      const event = {
        "requestContext": {
          "id": "DUMMY",
        }
      }
      await lambda.handler(event, context, () => {
      }).then(
        (data) => {
          assert.equal(data.isAuthorized, true)
        },
        (err) => {
          # FIXME: Lambda で error 発生時にテスト失敗としたい。
          # この書き方間違ってる気がするが、とりあえずテスト失敗には気がつける。
          assert.equal(0, 1, 'test failed.')
        })
    })
    afterEach(() => {
      sinon.restore()
    })
  })
})

テスト実施

$ npm i
$ npm test

補足

下書きのまま長期間熟成した記事なので色々古いと思います。