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

BGWeb's avatar
Level 7

Mail::assertSent failing even though mail is sent

I was hoping to figure this out without having to post but I'm what my wits end.

I've googled, checked stack overflow, and laracasts for an answer, but none of the posts I've found have a solution for my use case.

Very simply, I have a job that sends an email. I am trying to test that this email is being sent. What's puzzling me is that the email is going out (I am getting a copy in mailtrap, and logs when I switch the driver). For some reason, Mail::assertSent() refuses to return true.

Here's some code:

SendEmail job:

public function handle()
    {
        $emailTemplate = EmailTemplate::find($this->template);
        $storeArgument = isset($emailTemplate->arguments) ? $emailTemplate->arguments : [];
        $arguments = array_merge($this->arguments, $storeArgument, array("root_url" => URL::to('/')));

        $email = new EmailForQueuing(
            $emailTemplate->subject,
            $arguments,
            $emailTemplate->email_view,
            $this->category,
            $this->variables
        );

        Mail::to($this->to)->send($email);
    }

EmailTest class:

public function test_user_without_profile_receives_an_email_to_create_profile_instead_of_reset_password()
    {
        Queue::fake();
        Mail::fake();

        $user = User::factory()->create([
            'status' => 0
        ]);

        $response = $this->post('/password/email', [
            'email' => $user->email,
        ]);

        Queue::assertPushed(SendEmail::class);

        // Assert a message was sent to the given user...
        Mail::assertSent(EmailForQueuing::class);
    }

The failing test:

The expected [App\Mail\EmailForQueuing] mailable was not sent.
  Failed asserting that false is true.

I can place a dd() in the job before or after the Mail::to() call and see it in my terminal when I run my tests, so I know the code is being ran. Also, as I mentioned, the email is landing in Mailtrap.

I can not for the life of me figure out why the test is failing. Any feedback is greatly appreciated! Thank you :)

UPDATE

I setup a very simple route to test:

Route::get('/email', function () {
    $mail = new \App\Mail\EmailForQueuing(
        'test',
        [],
        'emails.default',
        '',
        []
    );

    Mail::send($mail);
});

and then updated my test:

{
 $this->get('/email')
Mail:assertSent(EmailForQueuing::class)
}

Which passes. Out of curiosity, I swapped out the logic in the route for the same logic I'm using in my controller to queue the email;

$data = [
                'to' => $user->email,
                'arguments' => [
                    'link' => $this->userService->getLoginLink($user),
                ],
                'template' => 'MustCreateProfileBeforePasswordReset'
            ];
            SendEmail::dispatch($data);

And now the test is failing again. So it seems that there is some disconnect between using a custom job to queue the mail.

P.S. I know there is no need to have a job to queue the email; the project I'm working on has many opportunities for refactoring and improvement, we're just not there yet.

P.S.S. I've watched this video https://laracasts.com/series/phpunit-testing-in-laravel/episodes/12 but haven't tried this approach as I'm hoping to avoid such a custom implementation and would prefer to use functionality available by default.

0 likes
2 replies
BGWeb's avatar
Level 7

UPDATE:

I can force the test to pass by constructing an instance of EmailForQueuing and calling Mail::to()->send() directly in the test. However, this doesn't seem appropriate because it's not testing the actual logic in the application that is sending the email.

$email = new EmailForQueuing(
            'test',
            ['link' => 'foobar.test'],
            'emails.create-profile-before-password-reset',
            '',
            '',
        );

        Mail::to($user->email)->send($email);
        // // Assert a message was sent to the given user...
        Mail::assertSent(EmailForQueuing::class);
1 like
alexm4c's avatar

For anyone else arriving here with the same issue here's how I fixed it.

Using this setup the test fails:

class TestCommand extends Command
{
    protected $signature = 'app:test-command';

    protected $description = 'Command description';

    public function handle()
    {
        Mail::to('[email protected]')->send(new SimpleMail(
            re: 'Test',
            body: 'test',
        ));
    }
}
class TestTest extends TestCase
{
    public function test_thing(): void
    {
        Mail::fake();

        $command = $this->artisan(TestCommand::class);
        $command->assertSuccessful();

        Mail::assertQueued(SimpleMail::class);
    }
}
   FAILED  Tests\Feature\TestTest > thing
  The expected [App\Mail\SimpleMail] mailable was not queued.
Failed asserting that false is true.

It's unclear in the docs but the artisan method uses a class called PendingCommand which runs the command and checks expectations when the test closes, ie. assertQueued is called before the command is run.

I was able fix this with the following change:

class TestTest extends TestCase
{
    public function test_thing(): void
    {
        Mail::fake();

        $command = $this->artisan(TestCommand::class);
        $command->assertSuccessful();
        $command->run();

        Mail::assertQueued(SimpleMail::class);
    }
}
   PASS  Tests\Feature\TestTest
  ✓ thing

Please or to participate in this conversation.