Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test mail sent from Notification in Laravel

My users can specify if they have Notifications sent to them instantly or to be notified daily of any pending Notifications.

The below works fine with manual testing; now I'd like to write a feature test around this to ensure that an email is sent and not an SMS or nothing.

public function test_instant_notification_is_sent(): void
{
    Mail::fake();

    $this->user = $user = factory(User::class)->create();

    $this->user->update(
        [
            'notification_frequency' => 'instant',
            'notification_method' => 'email',
            'notification_email' => $this->email,
        ]
    );

    $this->user->save();

    $email = ['subject' => 'subject', 'data' => 'data'];
    $sms = 'Test Notification';
    $database = ['some' => 'data'];

    $this->user->notify(new TestNotification($email, $sms, $database));

    Mail::assertSent(MailMessage::class);
}

I've written a TestNotification to go with it.

class TestNotification extends Notification
{
    use Queueable;

    public function __construct(array $email, string $sms, array $database)
    {
        $this->email = $email;
        $this->sms = $sms;
        $this->database = $database;
    }

    public function via($notifiable): array
    {
        return [$notifiable->preferredNotificationMethod()];
    }

    public function toDatabase($notifiable): array
    {
        return $this->database;
    }

    public function toNexmo($notifiable): array
    {
        return (new NexmoMessage)
            ->content($this->sms);
    }

    public function toMail($notifiable): MailMessage
    {
        return (new MailMessage)
            ->greeting($this->email['subject'])
            ->line('Testing Notifications!')
            ->line('Thank you for using our application!');
    }
}

My test fails because, as I now see, MailMessage is not a Mailable and I think Mail::fake() and the mail assertions only work with Mailable. Incidentally, if I remove Mail::fake() the email is sent fine.

How have others managed to test this please without actually sending the email?

like image 654
Ben Avatar asked Sep 02 '25 16:09

Ben


2 Answers

When testing Notifications, you'll want to use Notification::fake() and make those assertions. See the Notification Fake of the docs to see what I mean.

    public function test_instant_notification_is_sent(): void
    {
        Notification::fake();

        $this->user = $user = factory(User::class)->create();

        $this->user->update(
            [
                'notification_frequency' => 'instant',
                'notification_method' => 'email',
                'notification_email' => $this->email,
            ]
        );

        $this->user->save();

        $email = ['subject' => 'subject', 'data' => 'data'];
        $sms = 'Test Notification';
        $database = ['some' => 'data'];

        $this->user->notify(new TestNotification($email, $sms, $database));

        Notification::assertSentTo($this->user, TestNotification::class, function ($notification, $channels) {
            // check channels or properties of $notification here
            return in_array('mail', $channels);

            // or maybe check for nexmo:
            return in_array('nexmo', $channels);
        });
    }
like image 191
FatBoyXPC Avatar answered Sep 04 '25 08:09

FatBoyXPC


The answer turned out to be to check the via method as this is only set to one channel (in my case). And I didn't need to check the email was sent at all as @FatBoyXPC pointed out. Final solution:

       Notification::fake();

        $this->user->update(
            [
                'notification_frequency' => 'instant',
                'notification_method' => 'email',
                'notification_email' => $this->email,
            ]
        );
        $email = ['subject' => 'Test Subject', 'data' => 'data'];
        $sms = 'Test Notification';
        $database = ['some' => 'data'];

        $notification = new TestNotification($email, $sms, $database);

        $this->user->notify($notification);

        $this->assertEquals(['mail'], $notification->via($this->user));

now if I change email or instant this test fails and passes if email and instant

like image 31
Ben Avatar answered Sep 04 '25 08:09

Ben