2
votes

In a test I would like to make some assertions on a Mailable using Mail::assertSent(), like this:

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

So far, I have found the hasTo(), hasFrom(), hasBcc() and all the other has*() methods work just great. However, when asserting a particular attribute on the Mailable exists, for example subject the attribute shows up as null and the assertion fails:

Mail::assertSent(MyMailable::class, function ($mail) {
    return $mail->subject === 'My Subject';
});

I believe this is because I have configured all of the Mailable attributes within the build() method which at the stage of the assertion probably hasn't been invoked, so the attributes are not set on the object yet.

I thought using the build() method was the correct approach to take based on the docs:

All of a mailable class' configuration is done in the build method. Within this method, you may call various methods such as from, subject, view, and attach to configure the email's presentation and delivery.

https://laravel.com/docs/5.5/mail#writing-mailables

I have found that I can get assertions on the Mailable's attributes working when I instead set the attributes on the constructor:

class MyMail extends Mailable
{
    public function __construct()
    {
        $this->subject = 'My Subject';
    }

    public function build() {
        return $this->subject('My Subject')->view('emails/my-email')
    }
}

However, I feel this approach is wrong because I feel like I am changing my code to suit my tests.

So, I would like to know if there is a better approach to making assertions against attributes on a Mailable? Any help would be most appreciated, thank you!

EDIT 1

Test class (irrelevant code stripped out)

/** @test */
function a_notification_is_sent_when_an_application_is_updated()
{
    Mail::fake([RequiresVerification::class]);

    // some set up and factory methods called here...

    // the listener for this event sends mail
    ApplicationUpdated::dispatch($application);

    // this assertion passes
    Mail::assertSent(RequiresVerification::class);

    // this assertion does not pass when subject is set on the build()  
    // method but passes when subject is set on the constructor
    Mail::assertSent(RequiresVerification::class, function ($mail) use ($user) {
        return $mail->subject === 'hello';
    });
}

EDIT 2

I am currently looking at the hasRecipient() method, which all has* methods use, to see how it handles making assertions against what I assume are Mailable attributes (to, from, bcc, cc, etc). Perhaps the Mailable object can be extended to add new attribute assertions using a similar approach?

https://github.com/laravel/framework/blob/5.5/src/Illuminate/Mail/Mailable.php#L540

1
Can you add the sample of your test file? Because actually in the test should invoking the send mail process. May be you can take a look at this documentation laravel.com/docs/5.5/mocking#mail-fake that is specific for Mailable testingDharma Saputra
@DharmaSaputra code from test has been added. The mail process is invoked by an event dispatched in the test. I have looked at the docs but they don't explain how to make assertions against the Mailable's attributes.haakym

1 Answers

2
votes

You can make assertions against attributes configured in the build() method by calling the build() method within the assertSent closure before making the assertions:

Mail::assertSent(MyMailable::class, function ($mail) {
    $mail->build();
    return $mail->subject === 'My Subject';
});

Thanks to @ohffs on laracasts for helping with this: https://laracasts.com/discuss/channels/testing/how-to-make-assertions-on-a-laravel-mailable