webサイトからメールを送る機能を実装することがしばしばあります。機能を実装するということはその機能についてテストしなければいけないということです。
このテストを実際にメールを飛ばすより手間がかからない方法で誤送信の起きるリスクのないローカルで行えるようにしたいです。この記事ではこれを実現するためのMailpitをLaravelの自動テストに組み込む方法を紹介します。
Mailpitは簡易に動かせるメールテストツールです。Mailpit単体でもバイナリで実行できてメールテスト用サーバーを立てられます。またDockerイメージも提供されておりDocker上で動かすことも容易です。私的にはメールテストをしたいプログラムと連携しやすく管理しやすいDockerの方がオススメです。
Mailpit – email & SMTP testing tool
MailpitとLaravelをDocker上で動かすためのDockerfileとdocker-compose.ymlの例が次です。
Laravel+Mailpit用のDockerfileとdocker-compose.yml
services: php: build: context: docker/php volumes: - ./:/var/www/html ports: - "8080:80" # Webサーバー。ブラウザで http://localhost:8080 でアクセスできます depends_on: - mailpit mailpit: image: axllent/mailpit ports: - "18025:8025" # Web UI。このポートでメールの結果を確認できます - "1025:1025" # SMTP。Dockerの外のプログラムからメール送信テストをする時に使用します
FROM php:8.4-apache # メールやテストに必要なライブラリなどを追加 RUN apt-get update && apt-get install -y \ git unzip libzip-dev zip curl \ && docker-php-ext-install pdo pdo_mysql # Composerインストール COPY --from=composer:2 /usr/bin/composer /usr/bin/composer # Laravel用にmod_rewrite有効化 RUN a2enmod rewrite # ApacheのDocumentRootをLaravelのpublicに変更 RUN sed -i 's|DocumentRoot /var/www/html|DocumentRoot /var/www/html/public|' /etc/apache2/sites-available/000-default.conf WORKDIR /var/www/html
この構成にするとPHP側はmailpit:1025に対してメールを送信し http://localhost:18025 で結果を目で確認できます。
Laravelで簡易にメール送信テストするための環境設定とコマンドのコードが次です。
MAIL_MAILER=smtp MAIL_HOST=mailpit MAIL_PORT=1025
<?php namespace App\Console\Commands; use Illuminate\Console\Command; class TestMail extends Command { protected $signature = 'app:test-mail {--to= : 宛先メールアドレス}'; protected $description = 'テストメールを1通送信します(宛先は --to オプションで指定)'; public function handle(): int { $to = $this->option('to'); if (!$to) { $this->error('宛先メールアドレスを --to で指定してください。'); return 1; } \Mail::raw('これはテストメールです。', function ($message) use ($to) { $message->to($to)->subject('テストメール'); }); $this->info("テストメールを送信しました(宛先: {$to})"); return 0; } }
環境設定に沿うように.envを変更し、コードを/app/Console/Commandsに置いてdocker compose exec php php artisan app:test-mail –to test@example.comと実行すると次のスクリーンショットのようにメールの送信結果が確認できます。
手作業でメール送信プログラムが適切か否かを確認するだけならこれだけで十分ですが自動テストにも使いたくなります。MailpitはAPIでMailpitが保持しているメールを検索、抽出でき、この機能を用いることで自動テスト上でメールが適切な内容で送られているかを確認できます。これは例えば次のように書けます。
<?php namespace Tests\Feature; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; use Illuminate\Support\Facades\Mail; use Tests\TestCase; class SendMailTest extends TestCase { public function testExample(): void { // メール送信 $result = \Artisan::call('app:test-mail', [ '--to' => 'test@example.com', ]); $this->assertEquals(0, $result); // 送信したメールの内容の確認 // /api/v1/messagesでメールの一覧を取得 // 簡易的な用途のために file_get_contents を使用していますが、本格的に利用する場合はAPIをクラスにまとめた方が便利です $json = file_get_contents('http://mailpit:8025/api/v1/messages'); $messages = json_decode($json, true)['messages'] ?? []; // 送信されたメールの中に目当てのメールがあるか確認しつつ削除のためにメールのIDを取得 $idList = collect($messages)->map(function ($message) { return $message['To'][0]['Address'] === 'test@example.com' && $message['Subject'] === 'テストメール' // スニペットは本文の先頭部分のため、内容が長い場合はそれっぽいのを見つけた後に詳細を見て確認した方が良いです && $message['Snippet'] === 'これはテストメールです。' ? $message['ID'] : null; })->filter()->values()->toArray(); $this->assertNotEmpty($idList, 'テストメールが送信されていません。'); // 送信したメールを削除 file_get_contents('http://mailpit:8025/api/v1/messages', false, stream_context_create([ 'http' => [ 'method' => 'DELETE', 'header' => [ 'accept: application/json', 'content-type: application/json', ], 'content' => json_encode(['IDs' => $idList]), ], ])); } }
このテストはdocker compose exec php php artisan test tests/Feature/SendMailTest.phpのようにして実行できます。
テストの実行に際してLaravelのデフォルトだとphpunit.xmlに<env name=”MAIL_MAILER” value=”array”/>というメーラーにSMTPを使わないようにする記述が入っているのでこれを外すかvalue=”smtp”にする必要があります。
こんな感じでMailpit内のメールを探して、特定のメールの有無をテストできます。Mailpitは他にも様々なAPIを持っており、これらの仕様は以下のドキュメントで参照できます