Jestを使ってalexa-sdkを使用したLambdaをテストする

この記事は一人Alexa Skills Kit for Node.js Advent Calendar 2017の11日目の記事です。 以前Mochaを使用したテストについてまとめましたが、今回はJestでもトライしてみ […]

広告ここから
広告ここまで

目次

    この記事は一人Alexa Skills Kit for Node.js Advent Calendar 2017の11日目の記事です。

    以前Mochaを使用したテストについてまとめましたが、今回はJestでもトライしてみます。
    Mocha & Power-assertでalexa-sdkで作ったAlexa Skillのユニットテスト

    インストール

    必要なものをインストールしましょう。

    $ npm init -y
    $ npm i -S alexa-sdk
    $ npm i -D jest power-assert
    

    ※power-assertはなくてもテストできますが、個人的趣味でいれてます。

    Jest実行方法

    Jestは以下のようにして実行することができます。

    $ ./node_modules/jest/bin/jest.js 
    No tests found
    In /Users/dev/alexa/alexa-hello-world
      8 files checked.
      testMatch: **/__tests__/**/*.js?(x),**/?(*.)(spec|test).js?(x) - 0 matches
      testPathIgnorePatterns: /node_modules/ - 8 matches
    Pattern:  - 0 matches
    

    Jestのテストコード

    Jestではデフォルトだと__tests__/ディレクトリ配下のファイルまたは***.test.jsという名称のファイルをテストファイルとみなします。

    例えばsample.test.jsに以下のように書いてみましょう。

    /* global test, expect */
    function sum (a, b) {
      return a + b
    }
    
    test('adds 1 + 2 to equal 3', () => {
      expect(sum(1, 2)).toBe(3)
    })
    

    これでテストコマンドを実行すると、以下のように結果が出ます。

    $ ./node_modules/jest/bin/jest.js 
     PASS  sample.test.js
      ✓ adds 1 + 2 to equal 3 (5ms)
    
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        1.329s
    Ran all test suites.
    

    あとはこれをalexa-sdkのスクリプト向けに書くだけです。

    jestでalexa-sdkを使ったコードをテスト

    テスト対象ファイルindex.js

    シンプルに、ask XXXと話しかけると「Hello World」と返すだけのスクリプトにします。

    const Alexa = require('alexa-sdk')
    const handlers = {
      LaunchRequest: function () {
        this.response.speak('Hello World')
        this.emit(':responseReady')
      }
    }
    
    module.exports.handler = function (event, context, callback) {
      const alexa = Alexa.handler(event, context)
      alexa.registerHandlers(handlers)
      alexa.execute()
    }
    

    Jestでのテストコード

    /* global describe, it */
    // ライブラリの読み込み
    const assert = require('power-assert')
    
    // テスト対象ファイルのロード
    const MyLambdaFunction = require('../../index.js')
    const { handler } = MyLambdaFunction
    
    // Lambdaのダミーevent
    const event = {
      'session': {
        'new': true,
        'sessionId': 'amzn1.echo-api.session.[unique-value-here]',
        'attributes': {},
        'user': {
          'userId': 'amzn1.ask.account.[unique-value-here]'
        },
        'application': {
          'applicationId': 'amzn1.ask.skill.[unique-value-here]'
        }
      },
      'version': '1.0',
      'request': {
        'locale': 'en-US',
        'timestamp': '2016-10-27T18:21:44Z',
        'type': '',
        'requestId': 'amzn1.echo-api.request.[unique-value-here]'
      },
      'context': {
        'AudioPlayer': {
          'playerActivity': 'IDLE'
        },
        'System': {
          'device': {
            'supportedInterfaces': {
              'AudioPlayer': {}
            }
          },
          'application': {
            'applicationId': 'amzn1.ask.skill.[unique-value-here]'
          },
          'user': {
            'userId': 'amzn1.ask.account.[unique-value-here]'
          }
        }
      }
    }
    
    // テストコード
    describe('hello alexa', () => {
      it('say hallo world', () => {
        event.request.type = 'LaunchRequest'
        const succeed = (data) => {
          const { response } = data
          const {
            outputSpeech
          } = response
          assert.deepEqual(
            outputSpeech,
            {
              type: 'SSML',
              ssml: '<speak> Hello World! </speak>'
            }
          )
        }
        const fail = (e) => {
          if (e.name === 'AssertionError') {
            assert.deepEqual(e.expected, e.actual)
          } else {
            assert.deepEqual(e, undefined)
          }
        }
        // eslint-disable-next-line handle-callback-err
        handler(event, {succeed, fail}, (error, data) => {})
      })
    })
    

    テスト実行結果

    $ ./node_modules/jest/bin/jest.js 
     PASS  sample.test.js
      hello alexa
        ✓ say hallo world (12ms)
    
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        1.404s
    Ran all test suites.
    
    

    SSMLの構文を間違えたりすると、このように出ます。

    $ ./node_modules/jest/bin/jest.js 
     FAIL  test/jest/index.test.js
      hello alexa
        ✕ say hallo world (26ms)
    
      ● hello alexa › say hallo world
    
        assert.deepEqual(received, expected)
    
        Expected value to deeply equal to:
          {"ssml": "<speak> Hello World! </speak>", "type": "SSML"}
        Received:
          {"ssml": "<speak> Hello World </speak>", "type": "SSML"}
    
        Difference:
    
        - Expected
        + Received
    
          Object {
        -   "ssml": "<speak> Hello World! </speak>",
        +   "ssml": "<speak> Hello World </speak>",
            "type": "SSML",
          }
    
          at Decorator.Object.<anonymous>.Decorator._callFunc (node_modules/empower-core/lib/decorator.js:110:20)
          at Decorator.Object.<anonymous>.Decorator.concreteAssert (node_modules/empower-core/lib/decorator.js:103:17)
          at Function.decoratedAssert [as deepEqual] (node_modules/empower-core/lib/decorate.js:51:30)
          at Object.fail (test/jest/index.test.js:28:16)
          at AlexaRequestEmitter.ValidateRequest (node_modules/alexa-sdk/lib/alexa.js:181:28)
          at HandleLambdaEvent.i18n.use.init (node_modules/alexa-sdk/lib/alexa.js:118:29)
          at node_modules/i18next/dist/commonjs/i18next.js:190:9
          at done (node_modules/i18next/dist/commonjs/i18next.js:281:21)
          at node_modules/i18next/dist/commonjs/i18next.js:302:7
          at I18n.loadResources (node_modules/i18next/dist/commonjs/i18next.js:238:7) thrown
    
    Test Suites: 1 failed, 1 total
    Tests:       1 failed, 1 total
    Snapshots:   0 total
    Time:        1.84s
    Ran all test suites.
    

    Tips: failのアサーションについて

    ここで紹介しているパターンでは、context.succeed()のコールバック内でアサーションを実行します。
    この方法だと、アサーションが失敗した場合にcontext.fail()でエラーハンドリングされてしまって表面上はテストが成功してしまいます。

    Hello World!ではなく日本語を想定したテストを書いてしまったケース

    describe('hello alexa', () => {
      it('say hallo world', () => {
        event.request.type = 'LaunchRequest'
        const succeed = (data) => {
          const { response } = data
          const {
            outputSpeech
          } = response
          assert.deepEqual(
            outputSpeech,
            {
              type: 'SSML',
              ssml: '<speak> こんにちは </speak>'
            }
          )
        }
        const fail = (e) => {}
        // eslint-disable-next-line handle-callback-err
        handler(event, {succeed, fail}, (error, data) => {})
      })
    })
    

    このケースではテストは失敗するべきです。
    しかしcontext.fail()に処理がないため、以下のようにテストが通過してしまいます。

    $ ./node_modules/jest/bin/jest.js 
     PASS  sample.test.js
      hello alexa
        ✓ say hallo world (15ms)
    
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        1.735s
    Ran all test suites.
    
    広告ここから
    広告ここまで
    Home
    Search
    Bookmark