💬

Laravelのメールのテストでの詰まった話

2022/10/20に公開
2

laravelのメールのテストで、宛先を指定するメールのテストが通らない問題で詰まったので、その話をします。

はじめに

GETで/mail/sendにアクセスしたら、メールを送信する仕様になっています。
TestMailというMailableクラスを作成して、その中で宛先や件名の設定をしています。

class TestMail extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct($name, $email)
    {
        $this->name = $name;
        $this->email = $email;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->to($this->email)//宛先の指定←ここに注目
            ->subject('テストタイトル')//件名の設定
            ->view('test.mail')
            ->with([
                'name' => $this->name,
            ]);
    }
}

コントローラーは以下の感じです。

class MailController extends Controller
{
    public function send(Request $request)
    {
        $name = 'テスト ユーザー';
        $email = 'test@example.com';

        Mail::send(new TestMail($name, $email));

        return view('welcome');
    }
}

テストを書いてみる

class EmailSendTest extends TestCase
{
    use RefreshDatabase;

    /** @test */
    function メールが送られるかのテスト()
    {
        $user = User::factory()->create();

        Mail::fake(); //メールを本当に送らないようにする

        $this->get('/mail/send')
            ->assertOk();

        // メールが送信されたかのテスト(OK)
        Mail::assertSent(TestMail::class);

        // メールが指定の宛先に送信されたかのテスト(テスト落ちてしまう)
        Mail::assertSent(TestMail::class, function ($mail) use ($user) {
            return $mail->hasTo($user->email);
        });
    }
}

問題点

上記のテストをすると以下のようなメールの送信されたかのテストは通りました。

//テスト通る
Mail::assertSent(TestMail::class);

しかし、以下の宛先指定のテストが落ちてしまいました。

//テスト通らない
Mail::assertSent(TestMail::class, function ($mail) use ($user) {
            return $mail->hasTo($user->email);
});

解決方法

試しに以下のように、MailableクラスであるTestMailで宛先を指定しせずに、コントローラーで宛先を指定してみると、、、、

//Mailファサードのtoで宛先を指定するように変更
Mail::to($email)->send(new TestMail($name, $email));

宛先指定のテストが通りました!

//テストが通るようになった!
Mail::assertSent(TestMail::class, function ($mail) use ($user) {
            return $mail->hasTo($user->email);
});

まとめ

この結果から、Mail::assertSentでのテストは、Mailableクラスをテストしているのではなく、
コントローラーで呼ばれた、Mailファサードのテストをしていたことがわかりました。

よって、メールの細かいテスト(件名やbccや宛先など)をしたい時は、Mailableクラスに情報を書くのではなく、Mailファサードに書きましょう。

追記

テストの内で$mail->build()をすると、Mailableクラスで宛先などを指定していても、テストが通りました。
ご指摘いただきありがとうございました。

Mail::assertSent(TestMail::class, function ($mail) use ($user) {
            $mail->build();
            return $mail->hasTo($user->email);
        });

ご指摘点や不明点などありましたら教えていただけると幸いです。

Discussion

hidenatsuhidenatsu

私の Laravel 8.X の環境ですが以下のようにbuild()を実行した後であればhasTo()などが使えています。

        Mail::assertSent(TestMail::class, function ($mail) use ($user) {
            $mail->build();
            return $mail->hasTo($user->email);
        });

テストのために書きたいコードが書けないのももどかしいと思うのでよければお試しください。

takumitakumi

$mail->build()したら、hasToのテスト通りました!
教えていただきありがとうございました。