Summer Sale! All accounts are 50% off this week.

nexxai's avatar
Level 37

Help testing events and mail

My goal is to create a table that contains a list of email address that we will never send email to. I found a guide on Ralph J Smit's site that by description seems to allow me to do exactly that.

I've created a listener:

<?php

namespace App\Listeners;

use Arr;
use App\Models\DoNotEmail;
use Illuminate\Mail\Events\MessageSending;
use Illuminate\Notifications\Events\NotificationSending;

class FilterEmailsSentToDoNotEmailAddressesListener
{
    /**
     * Handle the event.
     */
    public function handle(MessageSending|NotificationSending $event): null|bool
    {
        if ($event instanceof NotificationSending && !in_array('mail', $event->notification->via($event->notifiable))) {
            return null;
        }

        // Get the email address as a string
        $email = match ($event::class) {
            NotificationSending::class => $event->notifiable->routeNotificationFor('mail'),
            MessageSending::class => Arr::first($event->message->getTo())?->getAddress()
        };

        if (!$this->shouldSendEmail($email)) {
            return false;
        }

        return null;
    }

    protected function shouldSendEmail(string $address): bool
    {
        $found = DoNotEmail::where('address', $address)->get();

        if ($found) {
            return false;
        }

        return true;
    }
}

I've registered that listener in the EventServiceProvider:

    protected $listen = [
        Registered::class => [
            SendEmailVerificationNotification::class,
        ],
        MessageSending::class => [
            FilterEmailsSentToDoNotEmailAddressesListener::class,
        ],
        NotificationSending::class => [
            FilterEmailsSentToDoNotEmailAddressesListener::class,
        ]
    ];

But my problem is testing that it actually works.

If I try using Mail::fake(); in my test, like so:

    #[Test]
    public function if_we_attempt_to_email_an_address_in_the_do_not_email_list_it_will_not_be_sent(): void
    {
        $address = '[email protected]';
        DoNotEmail::factory()->create(['address' => $address]);
        $league = League::factory()->create();

        Mail::fake();

        Mail::to($address)->send(new NewSeasonNotification($league));

        Mail::assertNothingSent();
    }

The result is: The following mailables were sent unexpectedly: App\Mail\NewSeasonNotification

So I was thinking, "OK, maybe when you fake the Mail facade, the MessageSending / MessageSent events don't actually get triggered and so the event listener doesn't actually get a chance to intercept the call. What if instead of faking mail, I instead Event::fake(); and then watch for those events, making sure that Sending is triggered (since that's the event that kicks off our listener), but since the listener kicks back a false (the email address is in the DoNotEmail::where.... lookup), the MessageSent event should never happen, like this:"

    #[Test]
    public function if_we_attempt_to_email_an_address_in_the_do_not_email_list_it_will_not_be_sent(): void
    {
        $address = '[email protected]';
        DoNotEmail::factory()->create(['address' => $address]);
        $league = League::factory()->create();

        Event::fake();

        Mail::to($address)->send(new NewSeasonNotification($league));

        Event::assertDispatched(MessageSending::class);  // This should be dispatched
        Event::assertNotDispatched(MessageSent::class);  // This shouldn't be
    }

But then I get: The unexpected [Illuminate\Mail\Events\MessageSent] event was dispatched. and the email does end up showing in my inbox (in the actual test, I use my actual email address).

Can anyone shed some light into what might be happening and how I can better test this?

Thanks!

0 likes
1 reply

Please or to participate in this conversation.