
九保すこひです(フリーランスのITコンサルタント、エンジニア)
さてさて、少し前に個人的なサイトをつくって公開したんですが、その際に「あれ、メール送信のテストコードってどう書くんだっけ」となりました。
私がプログラムが好きなのでフロントエンドからバックエンド、DB設計、サーバー構築などいろんなことに触れる機会があるのですが、そうなるとたまに「トコロテン方式」ですっぽり抜け落ちてしまったりします…
そこで
せっかくいい機会なので最近大好きな「Laravel + React」でメール送信の「Feature テスト」をつくってみることにしました。
ぜひ何かの参考になりましたら嬉しいです。
「寒くて外出しないので
すっごく運動不足…
ドラムとかって運動になるのかな」
開発環境: Laravel 9.x、React、Vite、Inertia.js、TailwindCSS
目次 [非表示]
Feature テストとは??
いろいろな定義があるのかもしれませんが、今回の記事で言う「Feature テスト」とは、「とあるページにアクセス(データ送信)をしたらどうなった?」というような複数のメソッドが連動するテストとして考えてください。
逆に「ユニットテスト」は、関数やメソッドをひとつずつチェックするテストです。(今回ユニットテストは行いません)
前提として
以前このブログで公開した Laravel + React でお問い合わせフォームをつくる という記事を使ってメール送信の 「Feature テスト」を作っていきます。
なお、テストする内容は以下2つです。
- ちゃんとメール送信が実行されたか
- メール送信内容が正しいか
そして、紹介した「問い合わせフォーム」の流れは次のとおりです。
- お問い合わせフォームに入力
- 送信
- バリデーションに引っかかったら前のページへ、問題なければ完了ページへそれぞれリダイレクトする
では、これらを踏まえて楽しくやっていきましょう
テストコードを書くファイルをつくる
まずはテストコードを書くためのファイルを作成します。
以下のコマンドを実行してください。
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
おわりに
ということで、今回はいつもと違いテストコードにフォーカスして記事を書いてみました。
正直なところ、あまりテストを書くのが好きではなかったのですが、「おおっ 昔の私よ、ありがとう
」となった経験をしてからはむしろテストコードを書くのが好きになりました。
ぜひみなさんも「時間を超えた過去の自分へのありがとう」を体験してみてください。
ではでは〜
「過去の自分への
もうひとつの感謝は
大金払って海外留学したことです」