E2Eテストでメールを扱う

こんにちは。皆さんE2Eテスト書いてますか？
今回は自分がリグレッションテストの自動化に取り組む中で、特に自動化による恩恵が大きかった「メール周りのテスト自動化」について紹介したいと思います。

## やりたかったこと

ユーザー新規登録のE2Eテストを自動化したかったのですが、登録時にメールで送信される認証URLを受け取る必要がありました。
「登録時にユーザーにメールが送信される」という部分も自動テストで担保したかったので、ちゃんとメール送信を確認できるようにしたかった感じです。

## どうしたか

弊社ではメール送信に[Mandrill](https://mandrill.com/) を利用しています。
Mandrillには[TestMode](https://mandrill.zendesk.com/hc/en-us/articles/205582447-Does-Mandrill-Have-a-Test-Mode-or-Sandbox-)という機能があり、要はSandboxなのですが、「テスト用のAPIキーを使って送信したメールは実際に送信されることはない」という機能です。名前の通りテストに最適です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/112393/ce6e47be-38b5-8478-c8a5-068dd05a81f4.png)

こんな感じのイメージです。

## やってみよう

Mandrillの登録とかそういうのは本筋から逸れるんで省略します。ごめんなさい。

### MandrillのTestModeをONにする

右上のアカウント名のところをクリックすると `Turn on Test Mode` というボタンが出てくるのでクリックします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/112393/928986c6-db8e-4726-36ac-e887fcf513e5.png)

全体的に黄色い感じになりましたね。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/112393/4deae571-bf57-d275-24ff-7af584ee52df.png)

### テスト用APIキーを取得する

`Settings > New API Key` からAPIキーを作成します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/112393/f2c00310-fe9c-7eb2-4451-7966a74217b1.png)

このとき、必ず `Test Key` にチェックを入れて下さい。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/112393/675d3e9f-9c81-c5ef-f13b-042de3b83907.png)

### アプリからメールを送信する

上記で取得したAPI Keyを使ってメールを送信してください。
お使いのフレームワークなりでMandrill対応していればそのフレームワークのドキュメントを読んでもらうのが良いでしょうし、SMTPのパスワードにAPI Keyを入れてもOKです。

送信すると、`Outbound`タブからメールが見れるようになります。反映まで20〜30秒くらい時間がかかるようです。

### 送信したメールをAPIで確認する

Mandrill APIには[SDK](https://mandrillapp.com/api/docs/index.nodejs.html)が用意されているので、ありがたく使わせて頂きましょう。以下はNodeJSのサンプルです。検索結果から直近1件の明細を取得します。

```js
import { Mandrill } from 'mandrill-api';
import * as sleep from 'sleep-promise'

async function fetchOne(query, api_key) {
    const client = new Mandrill(api_key)
    const search = (query, api_key) => {
        return new Promise((resolve, reject) => {
            client.messages.search(
                {
                    query,
                    api_key,
                    limit: 100
                },
                result => {
                    resolve(result)
                }, 
                err => {
                    reject(err)
                }
            )
        })
    }
    let searchResult = await search(query, api_key)
    for (var i = 0; i < 10; i++) {
        if (searchResult['length'] === 0) {
            await sleep(10000)
            searchResult = await search(query, api_key)
        }
    }

    return new Promise((resolve, reject) => {
        client.messages.content(
            {
                id: searchResult[0]._id
            },
            result => {
                resolve(result)
            },
            err => {
                reject(err)
            },
        )
    })
}
```

前項でも書きましたが、メールを送ってからMandrillで確認できるようになるまで20〜30秒ほど時間がかかるので、何度かリトライするようにしています。
大まかな処理の流れとしては

- `search`でメールを検索する
- 1件以上結果が返ってくるまでリトライする
- 1件以上返ってきたら、メッセージIDをもとに `messages.content` を叩く

`messages.content` は非同期で返ってくるので、Promiseを返すようにしています。

`fetchOne()` の引数 `query` に指定するクエリは https://mandrill.zendesk.com/hc/en-us/articles/205583137-How-to-Search-Outbound-Activity-in-Mandrill を参照してください。例えば、「hoge@example.com 宛かつ件名が `ユーザー登録完了のお願い`」であれば下記のようになります。

```js

query = 'full_email:hoge@example.com AND subject:ユーザー登録完了のお願い'
```

### 戻り値をどうにかする

上記のサンプルを使って実際にメールを受け取ってみましょう。

```
console.log(await mail.fetchOne("full_email:hoge@example.com AND subject:ユーザー登録完了のお願い'"))
```

すると、こんな感じのJSONが出力されます。

```json
{
  "headers": {
    "Message-Id": "<2527db4835f0d47904dd1a28fb8afc85@swift.generated>",
    "Date": "Tue, 11 Dec 2018 15:51:43 +0900",
    "From": "webmaster@example.com",
    "To": "hoge@example.com",
    "Mime-Version": "1.0",
    "Content-Type": "text/html; charset=utf-8",
    "Content-Transfer-Encoding": "quoted-printable"
  },
  "html": "(省略)",
  "from_email": "webmaster@example.com",
  "from_name": "webmaster",
  "to": { "email": "hoge@example.com", "name": "" },
  "subject": "ユーザー登録完了のお願い",
  "attachments": [],
  "text": "(省略)",
  "ts": 1544511103,
  "tags": [],
  "_id": "61aa5497443d485a9747f1af8ab3d0d1"
}
```

メール本文は `html` や `text` に入りますので、ここをパースすれば今回の目的である認証URLが取得できます。
注意点として、 `html` に入るURLはMandrill経由でリダイレクトされるURLになっていますので、特別な理由がなければ `text` から取るのが良いでしょう。

## おわりに

メール周りは手動テストするのもめんどくさいので、自動化で楽しやすい部分ですね！みなさんもどんどんやってみてください！