Skip to content

Instantly share code, notes, and snippets.

@JeffreyWay
Last active February 10, 2024 10:56
Show Gist options
  • Star 51 You must be signed in to star a gist
  • Fork 12 You must be signed in to fork a gist
  • Save JeffreyWay/b501c53d958b07b8a332 to your computer and use it in GitHub Desktop.
Save JeffreyWay/b501c53d958b07b8a332 to your computer and use it in GitHub Desktop.
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase
{
use MailTracking;
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
$this->visit('/route-that-sends-an-email')
->seeEmailWasSent()
->seeEmailSubject('Hello World')
->seeEmailTo('foo@bar.com')
->seeEmailEquals('Click here to buy this jewelry.')
->seeEmailContains('Click here');
}
}
<?php
trait MailTracking
{
/**
* Delivered emails.
*/
protected $emails = [];
/**
* Register a listener for new emails.
*
* @before
*/
public function setUpMailTracking()
{
Mail::getSwiftMailer()
->registerPlugin(new TestingMailEventListener($this));
}
/**
* Assert that at least one email was sent.
*/
protected function seeEmailWasSent()
{
$this->assertNotEmpty(
$this->emails, 'No emails have been sent.'
);
return $this;
}
/**
* Assert that no emails were sent.
*/
protected function seeEmailWasNotSent()
{
$this->assertEmpty(
$this->emails, 'Did not expect any emails to have been sent.'
);
return $this;
}
/**
* Assert that the given number of emails were sent.
*
* @param integer $count
*/
protected function seeEmailsSent($count)
{
$emailsSent = count($this->emails);
$this->assertCount(
$count, $this->emails,
"Expected $count emails to have been sent, but $emailsSent were."
);
return $this;
}
/**
* Assert that the last email's body equals the given text.
*
* @param string $body
* @param Swift_Message $message
*/
protected function seeEmailEquals($body, Swift_Message $message = null)
{
$this->assertEquals(
$body, $this->getEmail($message)->getBody(),
"No email with the provided body was sent."
);
return $this;
}
/**
* Assert that the last email's body contains the given text.
*
* @param string $excerpt
* @param Swift_Message $message
*/
protected function seeEmailContains($excerpt, Swift_Message $message = null)
{
$this->assertContains(
$excerpt, $this->getEmail($message)->getBody(),
"No email containing the provided body was found."
);
return $this;
}
/**
* Assert that the last email's subject matches the given string.
*
* @param string $subject
* @param Swift_Message $message
*/
protected function seeEmailSubject($subject, Swift_Message $message = null)
{
$this->assertEquals(
$subject, $this->getEmail($message)->getSubject(),
"No email with a subject of $subject was found."
);
return $this;
}
/**
* Assert that the last email was sent to the given recipient.
*
* @param string $recipient
* @param Swift_Message $message
*/
protected function seeEmailTo($recipient, Swift_Message $message = null)
{
$this->assertArrayHasKey(
$recipient, (array) $this->getEmail($message)->getTo(),
"No email was sent to $recipient."
);
return $this;
}
/**
* Assert that the last email was delivered by the given address.
*
* @param string $sender
* @param Swift_Message $message
*/
protected function seeEmailFrom($sender, Swift_Message $message = null)
{
$this->assertArrayHasKey(
$sender, (array) $this->getEmail($message)->getFrom(),
"No email was sent from $sender."
);
return $this;
}
/**
* Store a new swift message.
*
* @param Swift_Message $email
*/
public function addEmail(Swift_Message $email)
{
$this->emails[] = $email;
}
/**
* Retrieve the appropriate swift message.
*
* @param Swift_Message $message
*/
protected function getEmail(Swift_Message $message = null)
{
$this->seeEmailWasSent();
return $message ?: $this->lastEmail();
}
/**
* Retrieve the mostly recently sent swift message.
*/
protected function lastEmail()
{
return end($this->emails);
}
}
class TestingMailEventListener implements Swift_Events_EventListener
{
protected $test;
public function __construct($test)
{
$this->test = $test;
}
public function beforeSendPerformed($event)
{
$this->test->addEmail($event->getMessage());
}
}
@dericlima
Copy link

I solved! The application instance contains all Classes that I need, I could use Facades but I was looking for the Mail Class that is instantiated during the boot.
So, the solution was to retrieve the app instance:

$app = require __DIR__.'/../../bootstrap/app.php';
        $kernel = $app->make(Kernel::class);

        $response = $kernel->handle(
            $request = Request::capture()
        );

And to use the Mail:

$mailer = App::make('mailer');
        $mailer->getSwiftMailer()
            ->registerPlugin(new TestMailEventListener($this));

@mgodinez98
Copy link

@suth i have the same problem, when i send an email as raw the method getBody() always returns me null, so i made a function based on this post https://stackoverflow.com/questions/39926766/message-getbody-null-when-listening-on-mailer-sending-event

/**
     * Assert that the last email's body equals the given text.
     *
     * @param string $body
     * @param Swift_Message $message
     * @return $this
     */
protected function getMessageBody(Swift_Message $message)
    {
        if($htmlBody = $message->getBody()){
            return $htmlBody;
        }
        $children = $message->getChildren();

        if (isset($children[0]) && $children[0] instanceof \Swift_MimePart) {
            return $children[0]->getBody();
        }

        return null;
    }

and inside the seeEmailEquals i call this function

/**
     * Assert that the last email's body equals the given text.
     *
     * @param string $body
     * @param Swift_Message $message
     * @return $this
     */
    protected function seeEmailEquals($body, Swift_Message $message = null)
    {
        $this->assertEquals(
            $body, $this->getMessageBody($this->getEmail($message)),
            "No email with the provided body was sent."
        );
        return $this;
    }

I know this is not the best approach but it works for me :)

@Organizm238
Copy link

Organizm238 commented Jul 20, 2017

How to make this working for Laravel 5.4? I was getting error
A facade root has not been set.
I fixed it by putting $this->createApplication(); in setUpMailTracking method:

public function setUpMailTracking()
    {
        $this->createApplication();
        Mail::getSwiftMailer()->registerPlugin(new TestingMailEventListener($this));
    }

But now my beforeSendPerformed is not executed. What adjustments i need to do?

UPD: Just do the following and everything will work:

public function setUpMailTracking()
    {
        parent::setUp();
        Mail::getSwiftMailer()->registerPlugin(new TestingMailEventListener($this));
    }

@aminechraibi
Copy link

aminechraibi commented Jun 20, 2020

Removing
@‍‍‍‍‍before annotation from setUpMailTracking comment
resolve "RuntimeException : A facade root has not been set." problem

@jubagg
Copy link

jubagg commented Jan 9, 2021

Hello guys, I have the next problem with my mail testing.

1) Tests\Feature\ExampleTest::testBasicTest
ErrorException: Undefined property: Swift_Events_SendEvent::$getMessage

I follow the instrucctions of Jeffrey but my version of laravel it the version 7 and i think my problem come here

If anybody can help me i would apreciated. greetings!

@mberneis
Copy link

I used this code now for many years but the new mail transport in Laravel9 breaks it - Any recommendations (or an updated Laracast)? - I would love to replace only the Mailtracking class instead of rewriting all my tests...- I would love to replace only the Mailtracking class instead of rewriting all my tests...

@jstierney
Copy link

jstierney commented Aug 6, 2022

I made an attempt to adapt it to work with Laravel 9, which works for all my existing tests.

Since LogTransport no longer supports registerPlugin(), this needs ArrayTransport to work instead, so either set the default mailer to array in your .env file, i.e. MAIL_MAILER=array, or set it in runtime in your test (or setUp() method) before running the test: config()->set('mail.default', 'array');

Hope it helps.

<?php

namespace Tests\TestHelpers;

trait MailTracking
{
    /**
     * Delivered emails.
     */
    protected $emails = [];

    /**
     * Read messages from mailer and assign to emails array.
     */
    public function getMessages($overwrite = false)
    {
        if (! $overwrite && ! empty($this->emails)) {
            return $this;
        }
        $transport = app()->make('mailer')->getSymfonyTransport();
        if (method_exists($transport, 'messages')) {
            $this->emails = $transport->messages();
        }
        return $this;
    }

    /**
     * Assert that at least one email was sent.
     */
    protected function seeEmailWasSent()
    {
        $this->getMessages()->assertNotEmpty(
            $this->emails, 'No emails have been sent.'
        );
        return $this;
    }

    /**
     * Assert that no emails were sent.
     */
    protected function seeEmailWasNotSent()
    {
        $this->getMessages()->assertEmpty(
            $this->emails, 'Did not expect any emails to have been sent.'
        );
        return $this;
    }

    /**
     * Assert that the given number of emails were sent.
     */
    protected function seeEmailsSent($count)
    {
        $emailsSent = count($this->emails);
        $this->getMessages()->assertCount(
            $count, $this->emails,
            "Expected $count emails to have been sent, but $emailsSent were."
        );
        return $this;
    }

    /**
     * Assert that the last email's body equals the given text.
     */
    protected function seeEmailEquals($body)
    {
        $this->getMessages()->assertEquals(
            $body,
            $this->getEmailBody(),
            'No email with the provided body was sent.'
        );
        return $this;
    }

    /**
     * Assert that the last email's body contains the given text.
     */
    protected function seeEmailContains($excerpt)
    {
        $this->getMessages()->assertStringContainsString(
            $excerpt,
            $this->getEmailBody(),
            'No email containing the provided body was found.'
        );
        return $this;
    }

    /**
     * Assert that the last email's subject matches the given string.
     */
    protected function seeEmailSubject($subject)
    {
        $this->getMessages()->assertEquals(
            $subject, $this->lastEmail()->getSubject(),
            "No email with a subject of $subject was found."
        );
        return $this;
    }

    /**
     * Assert that the last email was sent to the given recipient.
     */
    protected function seeEmailTo($recipient)
    {
        $this->getMessages();
        $to = collect($this->lastEmail()->getTo())->map(fn ($item) => $item->getAddress())->toArray();
        $this->assertContains(
            $recipient,
            $to,
            "No email was sent to $recipient."
        );
        return $this;
    }

    /**
     * Assert that the last email was delivered by the given address.
     */
    protected function seeEmailFrom($sender)
    {
        $this->getMessages();
        $from = collect($this->lastEmail()->getFrom())->map(fn ($item) => $item->getAddress())->toArray();
        $this->assertContains(
            $sender,
            $from,
            "No email was sent from $sender."
        );
        return $this;
    }

    /**
     * Retrieve the mostly recently sent message.
     */
    protected function lastEmail()
    {
        $email = $this->emails->last();
        if ($email) {
            return $email->getOriginalMessage();
        }
    }

    /**
     * Retrieve the body of the most recent message.
     */
    protected function getEmailBody()
    {
        $email = $this->lastEmail();
        if ($email) {
            return $email->getTextBody() ?? $email->getHtmlBody();
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment