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.