Laravel + React のメール送信 Featureテストをつくる

こんにちは。フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、少し前に個人的なサイトをつくって公開したんですが、その際に「あれ、メール送信のテストコードってどう書くんだっけ❓❓」となりました。

私がプログラムが好きなのでフロントエンドからバックエンド、DB設計、サーバー構築などいろんなことに触れる機会があるのですが、そうなるとたまに「トコロテン方式」ですっぽり抜け落ちてしまったりします…😫

そこで❗

せっかくいい機会なので最近大好きな「Laravel + React」でメール送信の「Feature テスト」をつくってみることにしました。

ぜひ何かの参考になりましたら嬉しいです。😄✨


「寒くて外出しないので
すっごく運動不足…
ドラムとかって運動になるのかな🤔」

開発環境: Laravel 9.x、React、Vite、Inertia.js、TailwindCSS

Feature テストとは??

いろいろな定義があるのかもしれませんが、今回の記事で言う「Feature テスト」とは、「とあるページにアクセス(データ送信)をしたらどうなった?」というような複数のメソッドが連動するテストとして考えてください。

逆に「ユニットテスト」は、関数やメソッドをひとつずつチェックするテストです。(今回ユニットテストは行いません)

前提として

以前このブログで公開した Laravel + React でお問い合わせフォームをつくる という記事を使ってメール送信の 「Feature テスト」を作っていきます。

なお、テストする内容は以下2つです。

  • ちゃんとメール送信が実行されたか 🤔
  • メール送信内容が正しいか 🤔

そして、紹介した「問い合わせフォーム」の流れは次のとおりです。

  1. お問い合わせフォームに入力
  2. 送信
  3. バリデーションに引っかかったら前のページへ、問題なければ完了ページへそれぞれリダイレクトする

では、これらを踏まえて楽しくやっていきましょう❗

テストコードを書くファイルをつくる

まずはテストコードを書くためのファイルを作成します。
以下のコマンドを実行してください。

php artisan make:test ContactTest

※ なお、今回はContactTestとしていますが、これは今回のテーマが「お問い合わせ」なので「Contact」としています。この名前は自由に変更して問題ありません。ただ、****Testとしておいた方が検索しやすいのでおすすめです👍

すると、ファイルが作成されるのでこの中にテストコードを書いていくことになります。

「ちゃんとメール送信が実行されたか」をチェックするテストコード

では1つ目のテスト「メール送信が実行されたかどうか?」を確認するためのテストコードを見ていきましょう。

なお、今回は以前の記事で用意した以下4つの「お問い合わせの種類」すべて送信できるかチェックします。(つまり4回メール送信します)

  • 商品に関するお問い合わせ
  • ご注文に関するお問い合わせ
  • サポートに関するお問い合わせ
  • その他のお問い合わせ

tests/Feature/ContactTest.php

<?php

namespace Tests\Feature;

use App\Enums\ContactType;
use App\Mail\Contacted;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use Tests\TestCase;

class ContactTest extends TestCase
{
    public function test_can_send_contact_emails()
    {
        Mail::fake(); // 実際にはメールを送信しないようにする

        $contact_types = ContactType::collection();
        $uri = route('contact.store');

        foreach ($contact_types as $index => $contact_type) {

            $subject_id = $contact_type['id'];
            $params = [
                'subject_id' => $subject_id,
                'name' => Str::random(10),
                'email' => Str::random(10) .'@example.com',
                'message' => 'テストメッセージです! その '. $index + 1,
                'url' => 'https://example.com/'. Str::random(10),
            ];
            $response = $this->post($uri, $params);

            $response->assertStatus(302) // リダイレクトの HTTP ステータスコードだった?
                ->assertLocation(route('contact.complete')); // 完了ページにリダイレクトされた?

            $sent_count = $index + 1;
            Mail::assertSent(Contacted::class, $sent_count); // メールされた回数が合ってる?

        }
    }
    
    // ここに次のテストを書きます。
}

ではひとつずつ中身をご紹介します。

まず、Mail::fake()ですが、これはテスト用に用意されたもので実際にはメール送信されることはなくなります。

そして、ランダムなパラメータを作成&送信したあと「ちゃんとできてる?」を確認するコードをセットします。

まず以下の部分ですが、これは送信した際にページが返してきた「HTTPステータスコード」が302(リダイレクト)してるかどうかのチェックになっています。

->assertStatus(302)

※ HTTPステータスコードの一覧は、こちらをご覧ください。

ただし、注意が必要なのは今回の場合は「どちらにしても302が返ってくる」という部分です。つまり、バリデーションに失敗しても302になるんですね。

そのため、結論から言うと今回はassertStatus()は不要ですが、他のテストではよく使うのであえて紹介することにしました。(今回は例が悪かったかもです…😅)

そこで次の「移動先は合ってる?」チェックを入れています。

->assertLocation(route('contact.complete'))

そして、最後に「ちゃんとメール送信できた?」チェックです。

$sent_count = $index + 1;
Mail::assertSent(Contacted::class, $sent_count); // メールされた回数が合ってる?

Mail::assertSent()は、指定されたMailableが何回送信されたかをチェックしてくれます。

つまり、今回は全部で4回送信することになるので、その都度数値を$sent_countに入れてチェックしています。

では、この状態でテストしてみましょう。
以下のコマンドを実行してください。

php artisan test --filter ContactTest

※ なお、--filterを使うとテストしたいものだけ実行させることができます。また、test_can_send_contact_emailsというようなメソッド名でもOKです👍

すると・・・・・・

はい❗
テストに合格しました。

では、テストのテスト?(😂)として以下のようにわざと間違った数値を入れて実行してみましょう。

どうなったでしょうか・・・・・・

$sent_count = 99999; // 👈 ありえない数値で固定
Mail::assertSent(Contacted::class, $sent_count);

はい❗
見るからにエラー発生、という表示になりました。

ちなみに、もし英語が得意でない場合はあまり読みたくないかもしれませんが、ある程度きちんと理由を書いてくれているので、ここを手がかりにして何がダメなのかを判断すると開発を効率化できるでしょう👍(DeepLで翻訳するのもありですね)

メール送信内容が正しいかをチェックするテストコード

続いて、メールの送信内容が正しいのかをチェックするコードです。
先ほどと同じく4パターンのメール文面をチェックしてみましょう。

tests/Feature/ContactTest.php

// 省略

class ContactTest extends TestCase
{
    // 省略

    public function test_can_confirm_correct_email_content()
    {
        $contact_types = ContactType::collection();

        foreach ($contact_types as $index => $contact_type) {

            $subject = $contact_type['name'];
            $params = [
                'subject' => $subject,
                'name' => Str::random(10),
                'email' => Str::random(10) .'@example.com',
                'message' => 'テストメッセージです! その '. $index + 1,
                'url' => 'https://example.com/'. Str::random(10),
            ];
            $mailable = new Contacted($params);

            foreach ($params as $value) {

                $mailable->assertSeeInHtml($value); // パラメータがメールに含まれてる?

            }

        }
    }
}

この中でテストで使っているのは、Mailableのチェックです。

つまり、今回の例でいうと4つ作成されるメール文面に各パラメータが含まれているかを以下のループ内にあるassertSeeInHtmlでチェックしています。

foreach ($params as $value) {

    $mailable->assertSeeInHtml($value); // 👈 ここ

}

では、こっちのテストも実行してみましょう。
以下のコマンドを実行してください。

php artisan test --filter test_can_send_contact_emails

すると・・・・・・

はい❗
うまくいきました👍

では、今回も以下のように強制的にありえない文字列をいれて実行してみましょう。

$mailable->assertSeeInHtml("ありえない文字列です!!");

どうなるでしょうか・・・・・・

はい❗
同じく理由つきでエラーが出ました。

成功です😄

では、最後に一気に今回つくったテストを実行してみます。
以下のコマンドを実行してください。

php artisan test --filter ContactTest

すると・・・・・・

はい❗
テストが2つ実行され、どちらも合格しました。

すべて成功です✨😄👍

ちなみに1: メールに関するその他のチェックメソッド

今回はassertSeeInHtmlのみを使っていますが、メール内容によっては他のものを使うといいでしょう。詳しくは 本家のドキュメント をご覧ください。

  • assertSeeInHtml: HTML に入ってるよね?
  • assertDontSeeInHtml: HTML に入ってるないよね?
  • assertSeeInOrderInHtml: HTML にこの順番で入ってるよね?
  • assertSeeInText: テキストに入ってるよね?
  • assertDontSeeInText: テキストに入ってるないよね?
  • assertSeeInOrderInText: テキストにこの順番で入ってるよね?
  • assertHasAttachment: 添付ファイルあるよね?
  • assertHasAttachedData: 添付データあるよね?
  • assertHasAttachmentFromStorage: ローカルストレージから添付されてるよね?
  • assertHasAttachmentFromStorageDisk: クラウドストレージから添付されてるよね?

ちなみに2: テストのときだけメールドライバーを変更したい場合

ちなみにテストのときだけ、メールドライバー(どうやってメール送信を処理するか)を変更したい場合はphpunit.xmlの中身を変更してください。

phpunit.xml

<env name="MAIL_MAILER" value="array"/>

例えば、arrayだけでなく以下のものが使えます。(2022.11.16 現在)

  • smtp
  • sendmail
  • mailgun
  • ses
  • postmark
  • log
  • array
  • failover

※ ただし、Mail::fake()を使うとメール送信自体が実行されなくなるので、注意してください。

※ メール向けの設定だけでなく他の環境変数もここでセットできますよ👍

企業様へのご提案

今回のようにテストコードを書いておくと以下のようなメリットが期待できます。

  • バグや不具合の少ないシステムを構築できる
  • 全く関係がなさそうでも実は別のコードが実行される場合は多いものです。そのため、開発を進める度にテスト実行することで「過去のコード」でエラーが出ないかをチェックすることができます
  • チームで開発をする場合、自分以外の開発者が書いたコードに悪影響を与えてしまう場合も考えられます。しかし、テストコードがしっかりしていれば他の開発者に「書くべきではないコード」として知らせることができますし、未知の間違いを発見することにもつながります。

もしそういったテストコードをご用意されたい場合はぜひお問い合わせからご連絡ください。お待ちしております。m(_ _)m

開発のご依頼お待ちしております
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします! by 九保すこひ

おわりに

ということで、今回はいつもと違いテストコードにフォーカスして記事を書いてみました。

正直なところ、あまりテストを書くのが好きではなかったのですが、「おおっ❗ 昔の私よ、ありがとう😄✨」となった経験をしてからはむしろテストコードを書くのが好きになりました。

ぜひみなさんも「時間を超えた過去の自分へのありがとう」を体験してみてください。

ではでは〜❗


「過去の自分への
もうひとつの感謝は
大金払って海外留学したことです👍」

このエントリーをはてなブックマークに追加       follow us in feedly