Laravel unit testing emails

前端 未结 8 1383
难免孤独
难免孤独 2021-02-20 10:12

My system sends a couple of important emails. What is the best way to unit test that?

I see you can put it in pretend mode and it goes in the log. Is there something t

相关标签:
8条回答
  • 2021-02-20 10:38

    There are two options.

    Option 1 - Mock the mail facade to test the mail is being sent. Something like this would work:

    $mock = Mockery::mock('Swift_Mailer');
    $this->app['mailer']->setSwiftMailer($mock);
    $mock->shouldReceive('send')->once()
        ->andReturnUsing(function($msg) {
            $this->assertEquals('My subject', $msg->getSubject());
            $this->assertEquals('foo@bar.com', $msg->getTo());
            $this->assertContains('Some string', $msg->getBody());
        });
    

    Option 2 is much easier - it is to test the actual SMTP using MailCatcher.me. Basically you can send SMTP emails, and 'test' the email that is actually sent. Laracasts has a great lesson on how to use it as part of your Laravel testing here.

    0 讨论(0)
  • 2021-02-20 10:41

    For Laravel 5.4 check Mail::fake(): https://laravel.com/docs/5.4/mocking#mail-fake

    0 讨论(0)
  • 2021-02-20 10:44

    If any one is using docker as there development environment I end up solving this by:

    Setup

    .env

    ...
    MAIL_FROM       = noreply@example.com
    
    MAIL_DRIVER     = smtp
    MAIL_HOST       = mail
    EMAIL_PORT      = 1025
    MAIL_URL_PORT   = 1080
    MAIL_USERNAME   = null
    MAIL_PASSWORD   = null
    MAIL_ENCRYPTION = null
    

    config/mail.php

    # update ...
    
    'port' => env('MAIL_PORT', 587),
    
    # to ...
    
    'port' => env('EMAIL_PORT', 587),
    

    (I had a conflict with this environment variable for some reason)

    Carrying on...

    docker-compose.ymal

    mail:
        image: schickling/mailcatcher
        ports:
            - 1080:1080
    

    app/Http/Controllers/SomeController.php

    use App\Mail\SomeMail;
    use Illuminate\Http\Request;
    use Illuminate\Routing\Controller as BaseController;
    
    
    class SomeController extends BaseController
    {
        ...
        public function getSomething(Request $request)
        {
            ...
            Mail::to('someone@example.com')->send(new SomeMail('Body of the email'));
            ...
        }
    

    app/Mail/SomeMail.php

    <?php
    
    namespace App\Mail;
    
    use Illuminate\Bus\Queueable;
    use Illuminate\Mail\Mailable;
    use Illuminate\Queue\SerializesModels;
    
    class SomeMail extends Mailable
    {
        use Queueable, SerializesModels;
    
        public $body;
    
        public function __construct($body = 'Default message')
        {
            $this->body = $body;
        }
    
        public function build()
        {
            return $this
                ->from(ENV('MAIL_FROM'))
                ->subject('Some Subject')
                ->view('mail.someMail');
        }
    }
    

    resources/views/mail/SomeMail.blade.php

    <h1>{{ $body }}</h1>
    

    Testing

    tests\Feature\EmailTest.php

    use Tests\TestCase;
    use Illuminate\Http\Request;
    use App\Http\Controllers\SomeController;
    
    class EmailTest extends TestCase
    {
        privete $someController;
        private $requestMock;
    
        public function setUp()
        {
            $this->someController = new SomeController();
            $this->requestMock = \Mockery::mock(Request::class);
        }
    
        public function testEmailGetsSentSuccess()
        {
            $this->deleteAllEmailMessages();
    
            $emails = app()->make('swift.transport')->driver()->messages();
            $this->assertEmpty($emails);
    
            $response = $this->someController->getSomething($this->requestMock);
    
            $emails = app()->make('swift.transport')->driver()->messages();
            $this->assertNotEmpty($emails);
    
            $this->assertContains('Some Subject', $emails[0]->getSubject());
            $this->assertEquals('someone@example.com', array_keys($emails[0]->getTo())[0]);
        }
    
        ...
    
        private function deleteAllEmailMessages()
        {
            $mailcatcher = new Client(['base_uri' => config('mailtester.url')]);
            $mailcatcher->delete('/messages');
        }
    }
    

    (This has been copied and edited from my own code so might not work first time)

    (source: https://stackoverflow.com/a/52177526/563247)

    0 讨论(0)
  • 2021-02-20 10:44

    if you are using Notifcations in laravel you can do that like below

    Notification::fake();
    $this->post(...);
    $user = User::first();
    Notification::assertSentTo([$user], VerifyEmail::class);
    

    https://laravel.com/docs/7.x/mocking#notification-fake

    0 讨论(0)
  • 2021-02-20 10:51

    If you want to test everything around the email, use

    Mail::fake()
    

    But if you want to test your Illuminate\Mail\Mailable and the blade, then follow this example. Say, you want to test a Reminder email about some payment, where the email text should have product called 'valorant' and some price in 'USD'.

     public function test_PaymentReminder(): void
    {
        /* @var $payment SalePayment */
        $payment = factory(SalePayment::class)->create();
        auth()->logout();
    
        $paymentReminder = new PaymentReminder($payment);
        $html            = $paymentReminder->render();
    
        $this->assertTrue(strpos($html, 'valorant') !== false);
        $this->assertTrue(strpos($html, 'USD') !== false);
    }
    

    The important part here is ->render() - that is how you make Illuminate\Mail\Mailable to run build() function and process the blade.

    Another importan thing is auth()->logout(); - because normally emails being processed in a queue that run in a background environment. This environment has no user and has no request with no URL and no IP...

    So you must be sure that you are rendering the email in your unit test in a similar environment as in production.

    0 讨论(0)
  • 2021-02-20 10:57

    If you just don't want the e-mails be really send, you can turn off them using the "Mail::pretend(true)"

    class TestCase extends Illuminate\Foundation\Testing\TestCase {
        private function prepareForTests() {
          // e-mail will look like will be send but it is just pretending
          Mail::pretend(true);
          // if you want to test the routes
          Route::enableFilters();
        }
    }
    
    class MyTest extends TestCase {
        public function testEmail() {
          // be happy
        }
    }
    
    0 讨论(0)
提交回复
热议问题