E2Eテストでメールを扱う


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

やりたかったこと

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

どうしたか

弊社ではメール送信にMandrill を利用しています。
MandrillにはTestModeという機能があり、要はSandboxなのですが、「テスト用のAPIキーを使って送信したメールは実際に送信されることはない」という機能です。名前の通りテストに最適です。

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

やってみよう

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

MandrillのTestModeをONにする

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

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

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

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

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

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

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

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

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

Mandrill APIにはSDKが用意されているので、ありがたく使わせて頂きましょう。以下はNodeJSのサンプルです。検索結果から直近1件の明細を取得します。

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 を参照してください。例えば、「[email protected] 宛かつ件名が ユーザー登録完了のお願い」であれば下記のようになります。


query = 'full_email:[email protected] AND subject:ユーザー登録完了のお願い'

戻り値をどうにかする

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

console.log(await mail.fetchOne("full_email:[email protected] AND subject:ユーザー登録完了のお願い'"))

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

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

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

おわりに

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