Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

sirhxalot's avatar

How to Test if a verification email was queued?

Hi Folks!

I would like to write a PHPUnit test where I would like to proof if the verification email was send after register.

My goal is to override the email template used to send for the verification email therefore I have made the following changes:

App\User

    public function sendEmailVerificationNotification()
    {
        $this->notify((new VerificationEMail));
    }

App\Notifications\User\VerificationEMail

<?php

namespace App\Notifications\User;

use Illuminate\Auth\Notifications\VerifyEmail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class VerificationEMail extends VerifyEmail implements ShouldQueue
{
    use Queueable;

    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['mail'];
    }

    /**
     * Get the mail representation of the notification.
     *
     * @param  mixed $notifiable
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    public function toMail($notifiable)
    {
        $verificationUrl = $this->verificationUrl($notifiable);

        return (new MailMessage)
            ->subject(__('verify.mail.subject'))
            ->from(__('verify.mail.from'))
            ->markdown(
                'mail.user.verification', compact(
                    'verificationUrl'
                )
            );
    }

    /**
     * Get the array representation of the notification.
     *
     * @param  mixed $notifiable
     * @return array
     */
    public function toArray($notifiable)
    {
        return [
            //
        ];
    }
}

My test case looks like this:

    /** @test */
    public function it_queues_a_verification_notification_email()
    {
        $user = factory(User::class)
            ->make()
            ->makeVisible(['password']);

        Mail::fake();

        Mail::assertNothingSent();

        $this->put(
            route('auth.register.store'),
            $user->toArray()
        );

        Mail::assertQueued(VerifyEmail::class);
    }

I begin to make a user in order to have a valid user data. Than I send (put) the user to the register route which is the default register action from Laravel:

    public function register(SignupRequest $request)
    {
        event(new Registered($user = $this->create($request->all())));
        $request->flash('flash_success', __('register.success'));

        $this->guard()->login($user);

        if ($request->wantsJson()) {
            return response()
                ->json([
                    'message' => 'success',
                    'success' => true,
                ]);
        }

        return $this->registered($request, $user)
            ?: redirect()->back()->with([
                'message', 'success',
                'success' => true,
            ]);
    }

It works in produciton but not in testing. Why does my test not work? What should I do in order to test if the verification email was send to the queue and finally to the user?

Thank you in advance and best regards Alexander (sirthxalot)

0 likes
21 replies
sirhxalot's avatar

Besides I have added the following Queue Connection for testing purposes:

QUEUE_CONNECTION=sync
QUEUE_DRIVER=sync
Sti3bas's avatar

@sirhxalot do you get any errors if you put $this->withoutExceptionHandling(); at the top of your test?

sirhxalot's avatar

I know that the put action works from other tests where I check if a new user has been created. This is why i am sure that there is no validation error.

sirhxalot's avatar

No. I only have the failed assertion and no errors so far:

The expected [Illuminate\Auth\Notifications\VerifyEmail] mailable was not queued.
Failed asserting that false is true.
Sti3bas's avatar

@sirhxalot your class names doesn't match, you are expecting VerificationEMail, but you're asserting that VerifyEmail was queued.

Mail::assertQueued(VerifyEmail::class);

should be

Mail::assertQueued(VerificationEMail::class);

sirhxalot's avatar

Besides now I am testing if the original verification email was sent. I have also tested if my custom Notification was sent:

Mail::assertQueued(VerificationEMail::class);

I have also tried out Queue::assertPushed and Notification::assertSentTo.

Sti3bas's avatar

@sirhxalot well, VerifyEmail is not a Mail, it's a Notification:

This should work:

/** @test */
    public function it_queues_a_verification_notification_email()
    {
        $user = factory(User::class)
            ->make()
            ->makeVisible(['password']);

        Notification::fake();

        Notification::assertNothingSent();

        $this->put(
            route('auth.register.store'),
            $user->toArray()
        );

        Notification::assertSentTo(User::first(), VerificationEMail::class);
    }

If you want to add assertions for the email sent through the notification, you can pass a callback as a third parameter:

Notification::assertSentTo(User::first(), VerificationEMail::class, function($notification) use ($user) {
   $mail = $notification->toMail($user);

   return $mail->subject === 'Test subject';
});
2 likes
sirhxalot's avatar

Thank you. I have done this aswell and also Queue.

Fact is my custom verification email is a notification that implement shoulqueue. Its not a mail but as I said i have tried out all possible mockings.

sirhxalot's avatar

I have now tried out the following:

$this->expectsEvents('Illuminate\Auth\Events\Verified');

Even this assertion fails? How can I trigger the event correctly?

Sti3bas's avatar

@sirhxalot what do you mean? Verified event is triggered after you verify your email. Registered event should be triggered after you register:

$this->expectsEvents(\Illuminate\Auth\Events\Registered::class);

sirhxalot's avatar

@sti3bas - Thank you!

Event Verified

When I make the assertion $this->expectsEvents('Illuminate\Auth\Events\Verified');

I will receive the following console output:

1) Tests\Feature\SignUpTest::it_queues_a_verification_notification_email
These expected events were not fired: [Illuminate\Auth\Events\Verified]
Failed asserting that an array is empty.

Event Registred

The assertion you shared: $this->expectsEvents(\Illuminate\Auth\Events\Registered::class); turns into the same error:

1) Tests\Feature\SignUpTest::it_queues_a_verification_notification_email
These expected events were not fired: [Illuminate\Auth\Events\Registered]
Failed asserting that an array is empty.

Conclusion

This brings me to the conclusion that the events will not be triggered during testing. But why?

sirhxalot's avatar

And just to eliminate any missunderstandings this is the current implementation of the test:

    public function it_queues_a_verification_notification_email()
    {
        $user = factory(User::class)
            ->make()
            ->makeVisible(['password']);

        Notification::fake();

        Notification::assertNothingSent();

        $this->withoutExceptionHandling();

        $this->put(
            route('auth.register.store'),
            $user->toArray()
        );

        $this->expectsEvents(\Illuminate\Auth\Events\Registered::class);

        // Notification::assertSentTo($user, VerificationEMail::class);
    }
Sti3bas's avatar

@sirhxalot $this->expectsEvents(\Illuminate\Auth\Events\Registered::class); should be at the top, not at the bottom.

sirhxalot's avatar

@sti3bas you are right!

When I put the expectsevents to the top it turns greeen.

But when I uncomment the Notification assertion sent to it still fails:

1) Tests\Feature\SignUpTest::it_queues_a_verification_notification_email
The expected [App\Notifications\User\VerificationEMail] notification was not sent.
Failed asserting that false is true.

Current implementation:

    public function it_queues_a_verification_notification_email()
    {
        $this->expectsEvents(\Illuminate\Auth\Events\Registered::class);

        $user = factory(User::class)
            ->make()
            ->makeVisible(['password']);

        Notification::fake();

        Notification::assertNothingSent();

        $this->withoutExceptionHandling();

        $this->put(
            route('auth.register.store'),
            $user->toArray()
        );

        Notification::assertSentTo($user, VerificationEMail::class);
    }
Sti3bas's avatar

@sirhxalot change Notification::assertSentTo($user, VerificationEMail::class); to Notification::assertSentTo(User::first(), VerificationEMail::class); to fetch the user from the database.

sirhxalot's avatar

Unfortunally same failure.

I have dump and died: User::first() and $user both give me a App\User eloquent class.

Sti3bas's avatar

@sirhxalot do you really tried to add $this->withoutExceptionHandling(); at the top? Do you have password confirmation validation rule set in your SignupRequest?

sirhxalot's avatar

This is my testing environment:

APP_NAME="A cool Name"
APP_ENV=testing
APP_KEY=base64:w9ZOK0PIt4/xmNb5T7WRBtzG4hAVPrbUphhSnx9OlEo=
APP_URL=http://localhost

STRIPE_KEY=not_for_public
STRIPE_SECRET=not_for_public

DB_CONNECTION=sqlite_testing
DB_DATABASE=":memory:"

BCRYPT_ROUNDS=4

CACHE_DRIVER=array
MAIL_DRIVER=array
SESSION_DRIVER=array
BROADCAST_DRIVER=null

QUEUE_CONNECTION=sync
QUEUE_DRIVER=sync

CYON_LOG="log-file-name"

Any errors with that?

sirhxalot's avatar

Yes I did. And I am 99% sure that there is no error with the registration itself.

The following assertion works this is why I am confident that the registration itself works:

    public function its_stores_a_new_user()
    {
        $user = factory(User::class)
            ->make()
            ->makeVisible(['password'])
            ->toArray();

        $this->assertDatabaseMissing(
            'users', [
            'email' => $user['email']
            ]
        );

        $this->put(
            route('auth.register.store'),
            $user
        );

        $this->assertDatabaseHas(
            'users', [
            'email' => $user['email']
            ]
        );
    }

This turns into green.

sirhxalot's avatar
sirhxalot
OP
Best Answer
Level 9

@sti3bas Thank you for your help! I have found out what the problem was:

The assertion sent to used a user that doesn't exists. I had to fetch the genereated user and than do the assertion - so the assertion end up like this:

    public function it_queues_a_verification_notification_email()
    {
        $user = factory(User::class)
            ->make()
            ->makeVisible(['password']);

        Notification::fake();

        Notification::assertNothingSent();

        $this->put(
            route('auth.register.store'),
            $user->toArray()
        );

        $user = User::firstWhere('email', $user->email);

        Notification::assertSentTo($user, VerificationEMail::class);

        Notification::assertTimesSent(1, VerificationEMail::class);
    }

Using the $this->expectsEvents(\Illuminate\Auth\Events\Registered::class); was not a good idea since it fakes the events so after this line the event will never triggered.

Stupid mistake I am sorry for that.

5 likes

Please or to participate in this conversation.