Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test an email's subject with laravel's Mailable

I'm wondering, if there is an easy and direct way to test the email's subject using the new Mailable function of Laravel

I have a class which can send 5 different emails, but all of them to the same person, so that testing if an email was sent to a concrete person is not really a complete test. I need to know if a concrete email was sent to a concrete person.

I think, using the email's subject to test the uniqueness is the best way. If you have a better way please tell me.

I have this:

Mail::assertSentTo([Config::get('mail.backend')], SendEmail::class);

And I would like to add something like this

Mail::assertSubject('My Subject', SendEmail::class);

Is there a way to do this??

Thank you!

like image 575
Fran Roa Prieto Avatar asked Dec 19 '22 09:12

Fran Roa Prieto


2 Answers

Edit

Quick and better solution than mine found at How to make assertions on a Laravel Mailable, thanks to @haakym

Just call the build() method before asserting:

Mail::assertSent(OrderShipped::class, function ($mail) use ($user) {
            $mail->build();
            return $mail->subject = 'The SUBJECT that I very much need';
        });

Any way I still prefer

Mail::assertSent(InfoRequestMailable::class, function ($mail) {
    $mail->build();
    $this->assertEquals(env('MOREINFO_MAILBOX'), $mail->to[0]['address'], 'The message wasn\'t send to the right email');
    $this->assertEquals('Quite a subject', $mail->subject, 'The subject was not the right one');
    return true;
});

My original post

I see this question has been here for a while, but I stumbled into the same thing.

(Important: of all this is for testing one single mail).

Using Laravel 5.6

In the docs about mocking a Mail you can see:

use Illuminate\Support\Facades\Mail;

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

        // Perform order shipping...

        // Assert a message was sent to the given users...
        Mail::assertSent(OrderShipped::class, function ($mail) use ($user) {
            return $mail->hasTo($user->email) &&
                   $mail->hasCc('...') &&
                   $mail->hasBcc('...');
        });

    }
}

This would ease any solution, right? You should be able to do something like:


Mail::assertSent(OrderShipped::class, function ($mail) use ($user) {
            return $mail->subject = 'The SUBJECT that I very much need';
        });

and that should work. Right? Well, it wont unless you do things a little different.

IMO the source of the problem

In the Mail guide, every example they present uses the build method:

public function build()
{
    return $this->from('[email protected]')
                ->subject('The SUBJECT that I very much need')
                ->view('emails.orders.shipped');
}

The thing is that when you call Mail::fake() at the top of your method you turn the whole Mail system into a Illuminate\Support\Testing\Fakes\MailFake (that's why it supports the assertSent method amongst others), and this implies that the custom build function never gets called.

Solution

You should start using more the __constructor method on the Mailable class. And just return the instance in the build() method:

Following (and modifying) the view example in the Mail guide:

namespace App\Mail;

use Illuminate\Mail\Mailable;

class OrderShipped extends Mailable
{
    ...

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct(Order $order)
    {
        $this->order = $order;
        $this->view('emails.orders.shipped');
        $this->subject('The SUBJECT that I very much need');
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this;
    }
}

This being said, now this works:

Mail::fake();
...
Mail::assertSent(OrderShipped::class, function ($mail) use ($user) {
            return $mail->subject == 'The SUBJECT that I very much need';
        });

PS

I'll rather do something like this, because I feel the control is much more grained:

class MailControllerTest extends oTestCase
{
    public function testMoreInfo()
    {
        Mail::fake();

        // send the mail

        Mail::assertSent(InfoRequestMailable::class, function ($mail) {
            $this->assertEquals(env('MOREINFO_MAILBOX'), $mail->to[0]['address'], 'The message wasn\'t send to the right email');
            $this->assertEquals('Quite a subject', $mail->subject, 'The subject was not the right one');
            return true;
        });
    }
}

The way assert works in unit testing will never walk by a wrong condition. :)

like image 94
Juan Javier Triff Cabanas Avatar answered Jan 11 '23 08:01

Juan Javier Triff Cabanas


With 5.4

To perform the test you have in your question you would do:

Mail::assertSent(SendEmail::class, function ($mail) {
    return $mail->hasTo(Config::get('mail.backend'));
});

to test that the subject you could do something like:

Mail::assertSent(SendEmail::class, function ($mail) {
    return $mail->hasTo(Config::get('mail.backend'))
        && $mail->subject == 'My Subject';
});

https://laravel.com/docs/5.4/mocking#mail-fake

Hope this helps!

like image 30
Rwd Avatar answered Jan 11 '23 09:01

Rwd