4

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!

Fran Roa Prieto
  • 385
  • 1
  • 3
  • 12

3 Answers3

10

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('example@example.com')
                ->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. :)

halfer
  • 19,824
  • 17
  • 99
  • 186
2

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!

Rwd
  • 34,180
  • 6
  • 64
  • 78
  • Hi, thank you for the answer. Apparently the MailFake function has changed a little from versions 5.3 to 5.4 With 5.3 I had to do that: **Mail::assertSent(SWIFTListImportFailedEmail::class, function ($mail) { return $mail->emailSubject == 'SWIFT IMPORT FAILED'; });** and then **Mail::assertSentTo(['test@testemail.com'], SWIFTListImportFailedEmail::class);** Thank you! – Fran Roa Prieto Feb 08 '17 at 12:37
  • @FranRoaPrieto Sorry about that, I thought you were using 5.4. – Rwd Feb 08 '17 at 14:44
  • Don't worry, you gave me the clue when I saw the link you shared with me Thanks! I see you have commented me. To comment someone you have only to remove the spaces? Does it work?: @RossWilson – Fran Roa Prieto Feb 09 '17 at 09:11
1

Note: My Laravel Version: 6.x

You need to call build() method before asserting subject. Here, I am asserting email is sent to authenticated users and subject is Payment Deposit Notification

Mail::assertQueued(DepositePaymentMail::class, function ($mail) {
    $mail->build();
    return $mail->hasTo(auth()->user()->email) &&
        $mail->subject == 'Payment Deposit Notification';
});

and my build function of Mailable class looks like:


public function build()
{
    return $this->subject('Payment Deposit Notification')
        ->markdown(
            'email.company.deposite-payment-request'
        );
}
Bedram Tamang
  • 3,748
  • 31
  • 27