LaravelのMailableはメール送信に便利なクラスですが、テストでも同様にMailableをアサートするための便利なメソッドが用意されています。この記事では、Laravel10系の新しいMailableを使ったメール送信テストの書き方をご紹介します。
新Mailableでのメール送信についてはこちらの記事をご覧ください。
テスト対象
テスト対象は以下のクラスです。送信先のメールアドレスを受け取って送信するシンプルなものです。
class Message extends Model
{
    public static function sendMail($email)
    {
        Mail::to($email)->send(new BuildMail);
    }
}
sendの引数に渡しているBuildMailがMailableを継承しているクラスとなり、以下のように定義しています。
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Attachment;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class BuildMail extends Mailable
{
    use Queueable, SerializesModels;
 
    /**
     * Create a new message instance.
     */
    public function __construct()
    {
        //
    }
 
    /**
     * Get the message envelope.
     */
    public function envelope(): Envelope
    {
        return new Envelope(
            subject: 'Test Mail',
            from: 'from@example.com',
        );
    }
 
    /**
     * Get the message content definition.
     */
    public function content(): Content
    {
        return new Content(
            html: 'emails.test',
        );
    }
 
    /**
     * Get the attachments for the message.
     *
     * @return array<int, \Illuminate\Mail\Mailables\Attachment>
     */
    public function attachments(): array
    {
        return [
            Attachment::fromPath(
                storage_path('app/img/testmail.jpg')
            ),
        ];
    }
}
メールの送信回数をテスト
まず、メールが1回送信されたことをテストしてみましょう。テストコードは以下のようになります。
   public function sendMailTest()
    {
        Mail::fake();
        //テスト対象の関数を実行
        Message::sendMail('to@example.com');
        Mail::assertSent(BuildMail::class, 1);
    }
最初にMail::fake()を使用しています。テストでは実際にメールを送信する必要はないので、メール送信をモック化し実際に送信されることを防いでいます。
Mail::assertSent()では、メールが送信されたかどうか、またその回数をアサートしています。第一引数がメール送信クラス、第二引数が期待する送信回数です。今回は1回送信されることを確認するので、1としています。
これでテストを実行すると、無事OKとなりました。
% vendor/bin/phpunit --filter sendMailTest PHPUnit 10.4.1 by Sebastian Bergmann and contributors. . 1 / 1 (100%) Time: 00:00.447, Memory: 26.00 MB OK (1 test, 1 assertions)
もし期待と異なりメールが2通送信された場合、以下のエラーが返ってきます。エラーメッセージにも送信数が違う旨が書いてあるので分かりやすいですね。
The expected [App\Mail\BuildMail] mailable was sent 2 times instead of 1 times. Failed asserting that 2 is identical to 1.
件名・宛先をテスト
件名や宛先など、より詳しく送信メールの情報をアサートしたい場合も同様にMail::assertSent()を使います。その際、以下のように第二引数のクロージャーでBuildMailクラスのインスタンスである$mail変数を受け取る必要があります。
        Mail::assertSent(BuildMail::class, function ($mail) {
            return $mail->hasTo('to@example.com') &&
                $mail->hasFrom('from@example.com') &&
                $mail->hasSubject('Test Mail');
        });
hasToで送信先のメールアドレスを、hasFromで送信元のメールアドレスを、またhasSubjectでメールの件名をそれぞれアサートしています。新・旧Mailableで関数名も同じですね。
テストを実行してみると、こちらもOKが出ました。
% vendor/bin/phpunit --filter sendMailTest PHPUnit 10.4.1 by Sebastian Bergmann and contributors. . 1 / 1 (100%) Time: 00:00.353, Memory: 26.00 MB OK (1 test, 1 assertions)
このテストの際、新Mailableになって便利になったと感じたことがあります。
旧Mailableでは、buildメソッドを使用しsubjectやfromを指定しているケースも少なくないと思います。その場合、テストコード内でもbuildの記述が必要でした。以下のようにです。
         Mail::assertSent(BuildMail::class, function ($mail) {
            $mail->build();
            return $mail->hasTo('to@example.com') &&....
        });
ですが、buildを使用せず新Mailableで提供されるようになったenvelopeやcontentを使用していれば、特に何も気にしなくともアサートは成功します。これはテストを書く立場として少し嬉しいです。
添付ファイルをテスト
次に、添付ファイルが期待通りか確認します。
BuildMailの定義でも使用していたAttachmentクラスを使うので、useをお忘れなく。
...
use Illuminate\Mail\Mailables\Attachment;
...
    public function sendMailTest()
    {
        ...//関数の実行など
        Mail::assertSent(BuildMail::class, function ($mail) {
            return $mail->hasAttachment(
                Attachment::fromPath(storage_path('app/img/testmail.jpg')),
            );
        });
    }
複数のメールを確認
1回で複数件送信されるメールのアサートも簡単です。以下は、計3通のメールが送信されたか、その宛先がそれぞれ期待通りかをアサートする場合のテストコードです。
        Mail::assertSent(BuildMail::class, 3);
        Mail::assertSent(BuildMail::class, function ($mail) {
            return $mail->hasTo('member1@example.com');
        });
        Mail::assertSent(BuildMail::class, function ($mail) {
            return $mail->hasTo('member2@example.com');
        });
        Mail::assertSent(BuildMail::class, function ($mail) {
            return $mail->hasTo('member3@example.com');
        });
このように、それぞれ順番にアサートを記述するだけで大丈夫です。こちらも新・旧Mailableで特に変わりはありません。
以上のように、新しいMailableでも旧とほとんど変わらない書き方でテストができます。どれもメールテストではよく使うものだと思いますので、ぜひご活用ください。
メルマガ購読の申し込みはこちらから。