nothingworksinc / ticketbeast

Back to the lesson videos:
https://course.testdrivenlaravel.com/lessons
552 stars 11 forks source link

render markdown mail #49

Closed Indemnity83 closed 7 years ago

Indemnity83 commented 7 years ago

Great little tip on rendering the mailable in the test to speed things up; looks like this only works with simple view emails though and not with markdown, looking through the mailable (and even the implementation of render in 5.5) it looks like there is no way to do this because all the variables and methods you need access to are protected/private.

Anybody see something I'm missing short of implementing the render method from 5.5 as a trait and pulling it into OrderConfirmtionEmail?

ohnotnow commented 7 years ago

As a related aside on markdown emails - I got caught last week with a markdown email that passed all my assertSent() tests, but on the real server it was throwing an exception as you need the php xml library installed (which it wasn't).

The app had been in production for about three months too - it was only by chance I thought to manually check the particular circumstance that triggered that mail. Live and learn... ;-)

sileence commented 7 years ago

@Indemnity83 I solved it by actually calling the send method in the mailable itself but "catching" the swift message using the array transport. You can see the code here:

https://gist.github.com/sileence/85298aa1a5f6a24026caba72fbdf5c88

On a side note, Mailable classes accept dependencies through the build method, so to cover more cases it might be necessary to use the Laravel Container to call the build method, like this:

$this->app->call([$mailable, 'build']);

Instead of simply calling build like this: $mailable->build().

adamwathan commented 7 years ago

@Indemnity83 I just tested the Markdown stuff in 5.5 and render does work there, here's a gist of the relevant files:

https://gist.github.com/adamwathan/ed8f3c6150f5b17e655b9d94517291bd

Get this in the browser:

image

Backporting the full render functionality to 5.4 is do-able, I submitted a PR to the Mailer a little while back to make it Macroable so we could do it 😄

Just add this macro to the Mailer:

        Mailer::macro('render', function ($view, $data = []) {
            list($view, $plain, $raw) = $this->parseView($view);
            $data['message'] = $this->createMessage();
            return $this->renderView($view, $data);
        });

...and then add a render method to each of your Mailables (or pull it in through a trait or similar):

    public function render()
    {
        Container::getInstance()->call([$this, 'build']);

        return Container::getInstance()->make('mailer')->render(
            $this->buildView(), $this->buildViewData()
        );
    }
Indemnity83 commented 7 years ago

Both great solutions; both evidence of the flexibility of Laravel :+1:

I ended up implementing the solution @sileence suggested primarily just because it was the first suggestion. I did slightly modify the trait to remove the dependency on the DomCrawler and return the body which makes the api act more like doing a $response = $this->get() and then asserting against $response.

It's really 6 of one and a half a dozen of the other. My impulse is that @adamwathan's solution might be preferable just so that when the application is updated to Laravel 5.5 you're only changing application code and not tests.

My tweaked 'InteractsWithMailable' trait:

<?php

namespace Tests;

use Illuminate\Contracts\Mail\Mailable;

trait InteractsWithMailable
{
    protected function open(Mailable $mailable)
    {
        $this->app->make('config')->set('mail.driver', 'array');

        $this->mailTransport()->flush();

        $this->app->make('mailer')->send($mailable);

        return $this->swiftMessages()->first()->getBody();
    }

    protected function swiftMessages()
    {
        $messages = $this->mailTransport()->messages();

        $this->assertNotEmpty($messages, 'No messages were sent');

        return $messages;
    }

    protected function mailTransport()
    {
        return $this->app->make('mailer')->getSwiftMailer()->getTransport();
    }
}

Then in the test I can make assertions against the body of the email:

<?php

namespace Tests\Unit\Mail;

use Config;
use App\Order;
use Tests\TestCase;
use Tests\InteractsWithMailable;
use App\Mail\OrderConfirmationEmail;

class OrderConfirmationEmailTest extends TestCase
{
    use InteractsWithMailable;

    /** @test */
    public function contains_a_link_to_order_confirmation_page()
    {
        $order = factory(Order::class)->make([
            'confirmation_number' => 'ORDERCONFIRMATION1234',
        ]);

        $email = $this->open(new OrderConfirmationEmail($order));

        $this->assertContains(url('/orders/ORDERCONFIRMATION1234'), $email);
    }