LocalStackを使って、Alexaスキルのテストをする方法

# はじめに
Alexaスキル開発では、S3にデータを保存することがよくあります。
S3を使うためには、AWSのCredentialが必要になるのですが、わざわざテストのために管理するのはめんどくさいです。

[LocalStack](https://github.com/localstack/localstack) と [ask-sdk-test](https://github.com/taimos/ask-sdk-test) を使って、いい感じにテストが出来る方法をやってみました。

# やってみた
TypeScript, Serverless Frameworkを利用していますが、細かい部分は省略しています。
JavaScriptを利用する場合は、適宜読み替えていただければと思います。

## LocalStack
LocalStackを使ってS3をローカルで動かします。
docker-composeを利用します。

```docker-compose.yml
version: '2.1'

services:
  localstack:
    container_name: "localstack_main"
    image: localstack/localstack
    ports:
      - "4566:4566"
    environment:
      - SERVICES=s3
```
[最近、利用ポートが一つにまとまったようです。](https://github.com/localstack/localstack#announcements)

---


ダミーのProfileを設定します。

```~/.aws/config
[default]
output =  json
region = ap-northeast-1

[profile localstack]
output = json
region = ap-northeast-1
```

```~/.aws/credentials
[default]
aws_access_key_id = dummy
aws_secret_access_key = dummy

[localstack]
aws_access_key_id = dummy
aws_secret_access_key = dummy
```

---

LocalStackを起動して、Alexaで使うS3のデータを作っておきます。

```bash
$ docker-compose up -d
$ aws --endpoint-url http://localhost:4566 --profile localstack s3 mb s3://sample-skill-test
$ echo '{"hello": "world"}' | aws --endpoint-url http://localhost:4566 --profile localstack s3 cp - s3://sample-skill-test/amzn1.ask.account.VOID
```

# Alexaスキル

一部の抜粋になりますが、ask-sdk-testが動くようにします。
（もしかしたら他にも必要なパッケージがあるかも... :pray: ）

```package.json
{
...
  "scripts": {
    "test": "npx mocha -r ts-node/register -r tsconfig-paths/register test/index.spec.ts",
  },
  "devDependencies": {
    "@types/mocha": "8.0.3",
    "ask-sdk-test": "^2.5.0",
    "mocha": "8.2.1",
...
  },
...
}

```

---


ハローワールドのスキルを修正して、S3からデータを引っ張ってきます。

```handler.ts
import AWS from "aws-sdk";

// s3ForcePathStyle を設定しないとhostの解決で失敗する
AWS.config.update({ region: "ap-northeast-1", s3ForcePathStyle: true });

// s3の設定
// testの時はLocalStackを使う
const s3PersistenceAdapter = new Adapter.S3PersistenceAdapter({
  bucketName: "sample-skill-test",
  s3Client: process.env.ENV == "test" ?
    new AWS.S3({endpoint: "http://localhost:4566"}) :
    new AWS.S3()
});

// handler
const LaunchRequestHandler = {
  canHandle(handlerInput: Ask.HandlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === "LaunchRequest";
  },
  async handle(handlerInput: Ask.HandlerInput) {
    const { hello } = await handlerInput.attributesManager.getPersistentAttributes();
    return handlerInput.responseBuilder
      .speak(hello)
      .withShouldEndSession(true)
      .getResponse();
  }
};

// Alexaの設定
export const alexa = Ask.SkillBuilders.custom()
  .addRequestHandlers(LaunchRequestHandler)
  .withApiClient(new Ask.DefaultApiClient())
  .withPersistenceAdapter(s3PersistenceAdapter)
  .lambda();
```

---

`world` というS3のデータが帰ってくるかを確認するテストです。

```test/index.spec.ts
import { describe } from "mocha";
import {
  AlexaTest,
  LaunchRequestBuilder,
  SkillSettings
} from "ask-sdk-test";

process.env.ENV = "test";

import { alexa as skillHandler } from "../handler.ts";

// initialize the testing framework
const skillSettings: SkillSettings = {
  appId: "amzn1.ask.skill.00000000-0000-0000-0000-000000000000",
  userId: "amzn1.ask.account.VOID",
  deviceId: "amzn1.ask.device.VOID",
  locale: "en-US"
};

const alexaTest = new AlexaTest(skillHandler, skillSettings);

describe("LaunchRequest", () => {
  alexaTest.test([
    {
      request: new LaunchRequestBuilder(skillSettings).build(),
      says: "world",
      repromptsNothing: true,
      shouldEndSession: true
    }
  ]);
});

```
---

テストを実行します。

```bash
$ npm run test
> npx mocha -r ts-node/register -r tsconfig-paths/register test/index.spec.ts
...

  LaunchRequest
    ✓ returns the correct responses (52ms)


  1 passing (60ms)
```


# まとめ
- LocalStackを使って、Alexaスキルのテストをローカル環境で動かしました。
- Github Actionsに組み込んでCIを作れたり、AWS費用がかからないのもよいです。
- LocalStackは、S3だけでなくDynamoDBなど他のAWSサービスにも対応しているので、いろいろ応用出来るかと思います。

# 参考
https://qiita.com/daisukeArk/items/45fb4cc5ca127f745aaa