Skip to content

Instantly share code, notes, and snippets.

@JeffreyWay

JeffreyWay/MailTracking.php Secret

Last active Mar 25, 2021
Embed
What would you like to do?
<?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());
}
}
@danijeel

This comment has been minimized.

Copy link

@danijeel danijeel commented Jan 21, 2016

    /**
     * Retrieve the appropriate swift message.
     *
     * @param Swift_Message $email
     */
    protected function getEmail(Swift_Message $email = null)
    {
        $this->seeEmailWasSent();
        // should be $email instead of $message
        return $email ?: $this->lastEmail();
    }
@JeffreyWay

This comment has been minimized.

Copy link
Owner Author

@JeffreyWay JeffreyWay commented Jan 21, 2016

Updated. Thanks!

@sebdesign

This comment has been minimized.

Copy link

@sebdesign sebdesign commented Jan 22, 2016

tests/phpunit.xml

<?xml version="1.0" encoding="UTF-8"?>
<phpunit ...>
    ...
    <php>
        ...
        <env name="MAIL_DRIVER" value="log"/>
    </php>
</phpunit>
@mattkoch614

This comment has been minimized.

Copy link

@mattkoch614 mattkoch614 commented Jan 22, 2016

@sebdesign Awwww yeah, I was just wondering how to do that.

@suth

This comment has been minimized.

Copy link

@suth suth commented Feb 12, 2016

The getBody() method used on Swift_Message didn't seem to be working (always returned null) when using Mail::raw() and plain text, so the seeEmailEquals and seeEmailContains assertions were always failing. Tried making an HTML view and using Mail::send() and everything worked beautifully.

@sebdesign

This comment has been minimized.

Copy link

@sebdesign sebdesign commented Apr 13, 2016

I added a method that asserts the message was BCC'd to the given recipients array.

/**
 * Assert that the last email was sent to the given recipients.
 *
 * @param array        $recipients
 * @param Swift_Message $message
 */
protected function seeEmailBcc(array $recipients, Swift_Message $message = null)
{
    $bcc = array_keys((array) $this->getEmail($message)->getBcc());

    sort($recipients);
    sort($bcc);

    $this->assertArraySubset(
        $recipients, $bcc,
        '',
        'No email was BCC\'d to: '.implode(', ', $recipients)
    );

    return $this;
}
@Tjoosten

This comment has been minimized.

Copy link

@Tjoosten Tjoosten commented Jun 29, 2016

But how does the Mail:: are defined. I'm stuck on that part.

@helmut

This comment has been minimized.

Copy link

@helmut helmut commented Jul 21, 2016

Jeffrey can you see this being added to the core? It's pretty dang useful...

@Oldenborg

This comment has been minimized.

Copy link

@Oldenborg Oldenborg commented Sep 7, 2016

This breaks down after I switched to 5.3, does any one have a patched version that works with 5.3 Mailables

@duartealexf

This comment has been minimized.

Copy link

@duartealexf duartealexf commented Sep 8, 2016

@olde86 It worked for me, using the 5.3 Mailables. Only thing I did was separate the event listener to its own file.

@dericlima

This comment has been minimized.

Copy link

@dericlima dericlima commented May 9, 2017

Looks like an amazing solution, I'm trying to implement this trait to use with Selenium, should be simple but I'm stuck.
Basically, I just moved the Email listener to a class itself and use the trait.

use Illuminate\Support\Facades\Mail;

class BasicFunctionalitiesTest extends PHPUnit_Extensions_Selenium2TestCase
{
    use MailTracking;

// Code

But I'm getting this error:

A facade root has not been set.

So I tried to create a container to solve:

        $app = new Container();
        $app->singleton('app', 'Illuminate\Container\Container');

        /**
         * Set $app as FacadeApplication handler
         */
        Facade::setFacadeApplication($app);

But no luck so far. Now I'm getting:

Class mailer does not exist

Some Idea? Thanks

@dericlima

This comment has been minimized.

Copy link

@dericlima dericlima commented May 10, 2017

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

This comment has been minimized.

Copy link

@mgodinez98 mgodinez98 commented Jun 28, 2017

@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

This comment has been minimized.

Copy link

@Organizm238 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

This comment has been minimized.

Copy link

@aminechraibi aminechraibi commented Jun 20, 2020

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

@jubagg

This comment has been minimized.

Copy link

@jubagg 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!

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