LaravelのMailableはメール送信に便利なクラスですが、テストでも同様にMailableをアサートするための便利なメソッドが用意されています。この記事では、Laravel10系の新しいMailableを使ったメール送信テストの書き方をご紹介します。

新Mailableでのメール送信についてはこちらの記事をご覧ください。

テスト対象

テスト対象は以下のクラスです。送信先のメールアドレスを受け取って送信するシンプルなものです。

class Message extends Model
{
    public static function sendMail($email)
    {
        Mail::to($email)->send(new BuildMail);
    }
}

sendの引数に渡しているBuildMailMailableを継承しているクラスとなり、以下のように定義しています。

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メソッドを使用しsubjectfromを指定しているケースも少なくないと思います。その場合、テストコード内でもbuildの記述が必要でした。以下のようにです。

         Mail::assertSent(BuildMail::class, function ($mail) {

            $mail->build();

            return $mail->hasTo('to@example.com') &&....
        });

ですが、buildを使用せず新Mailableで提供されるようになったenvelopecontentを使用していれば、特に何も気にしなくともアサートは成功します。これはテストを書く立場として少し嬉しいです。

添付ファイルをテスト

次に、添付ファイルが期待通りか確認します。

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でも旧とほとんど変わらない書き方でテストができます。どれもメールテストではよく使うものだと思いますので、ぜひご活用ください。

By hmatsu