Amazon AlexaAWS

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.

ブックマークや限定記事(予定)など

WP Kyotoサポーター募集中

WordPressやフロントエンドアプリのホスティング、Algolia・AWSなどのサービス利用料を支援する「WP Kyotoサポーター」を募集しています。
月額または年額の有料プランを契約すると、ブックマーク機能などのサポーター限定機能がご利用いただけます。

14日間のトライアルも用意しておりますので、「このサイトよく見るな」という方はぜひご検討ください。

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

Related Category posts