✉️

Laravelでメール送信時の属性をアサートする

2021/09/18に公開

背景

mocking#mail-fakeにあるように宛先はアサートできるが、その他の属性例えば件名やテンプレートなども同様にアサートしたい。(誰が、誰に、何の件名で、どのテンプレートを使って メール送信したか)

環境

Laravel: 8.24

内容

一般的なメールに含まれる属性を対象にする。 対象にした属性は以下の通り。

  • From
  • To
  • Subject
  • View

以下のような、Mailableクラスを継承したクラスを例に取る。

mail.php
/**
 * Build the message.
 *
 * @return $this
 */
public function build()
{
    return $this->from('example@example.com')
                ->markdown('emails.orders.shipped', [
                    'url' => $this->orderUrl,
                ])
		->subject('example subject');
}

一部抜粋したテストコードは以下の通り。

mailTest.php
<?php

namespace Tests;

use App\Mail\OrderShipped;
use Illuminate\Mail\Markdown;
use Illuminate\Support\Facades\Mail;
use Tests\TestCase;
use ReflectionClass;

class ExampleTest extends TestCase
{
    public function test_orders_can_be_shipped()
    {
        Mail::fake();

        // Perform sending email

        // Assert that a mailable with attributes sent
        Mail::assertSent($mailable, function ($mail) use ($data) {
            $mail->build();
            $reflectionClass = new ReflectionClass($mail);
            $reflectionProperty = $reflectionClass->getProperty('markdown');
            $reflectionProperty->setAccessible(true);
            $view = $reflectionProperty->getValue($mail);
	    
            $markdown = $this->app->make(Markdown::class);
            $body = $markdown->renderText($view, $mail->buildViewData());

            $this->assertSame($mail->viewData, $data['viewData']);

            return $mail->hasFrom($data['from'])
	        $mail->hasTo($data['to']) &&
                $mail->subject == $data['subject'] &&
                $view == $data['view'] &&
                ($body) &&
                strpos($body, $data['needle']);
    }
}
  • Mailaleクラスのビューテンプレートはprotectedなプロパティなので、 苦肉の策ではあるが Reflection で参照している。
  • markdown プロパティに指定されているテンプレートの妥当性は needle での文字列検索で担保している。(メールのレイアウトやコンテンツの完全一致はテスト観点に含めない)
  • viewData は連想配列形式であり、ユースケースによって項目名が可変するので assertSame を使っている。

注意点

  • From 属性をグローバル設定にしている場合は、 build() 実行時にはバインドされないのでアサートできないことに注意。

参考

Discussion