【问题标题】:Laravel unit testing emailsLaravel 单元测试邮件
【发布时间】:2020-09-28 12:52:23
【问题描述】:

我的系统发送了几封重要的电子邮件。对它进行单元测试的最佳方法是什么?

我知道你可以将它置于假装模式,它会进入日志。有什么要检查的吗?

【问题讨论】:

    标签: php laravel laravel-4


    【解决方案1】:

    有两种选择。

    选项 1 - 模拟邮件门面以测试正在发送的邮件。像这样的东西会起作用:

    $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());
        });
    

    选项 2 更容易 - 它是使用 MailCatcher.me 测试实际的 SMTP。基本上,您可以发送 SMTP 电子邮件,并“测试”实际发送的电子邮件。 Laracasts has a great lesson on how to use it as part of your Laravel testing here.

    【讨论】:

    • 我需要做些什么来运行断言吗?因为我有一个只运行 Mail::send() 的方法。如果我在此之后打电话,我的断言就不会上升。
    • 所以我发现假装导致它不起作用。所以我不得不把它关掉。谢谢@the-shift-exchange
    • BadMethodCallException: 方法 Mockery_0_Swift_Mailer::getTransport() 在这个模拟对象上不存在
    • 来自 Mockery_0_Swift_Mailer 的方法 send() 应该被准确地调用 1 次但被调用 0 次。
    【解决方案2】:

    “@The Shift Exchange”中的“选项 1”在 Laravel 5.1 中不起作用,所以这里是使用 Proxied Partial Mock 的修改版本:

    $mock = \Mockery::mock($this->app['mailer']->getSwiftMailer());
    $this->app['mailer']->setSwiftMailer($mock);
    $mock
        ->shouldReceive('send')
        ->withArgs([\Mockery::on(function($message)
        {
            $this->assertEquals('My subject', $message->getSubject());
            $this->assertSame(['foo@bar.com' => null], $message->getTo());
            $this->assertContains('Some string', $message->getBody());
            return true;
        }), \Mockery::any()])
        ->once();
    

    【讨论】:

    • 也适用于 Laravel 5.2 (Y)
    【解决方案3】:

    对于 Laravel 5.4 检查Mail::fake()https://laravel.com/docs/5.4/mocking#mail-fake

    【讨论】:

    • 它没有测试它存在电子邮件内部的问题。 Mailable->build() 永远不会执行,并且 email-blade 永远不会构建。
    【解决方案4】:

    如果您只是不想真正发送电子邮件,可以使用“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
        }
    }
    

    【讨论】:

    • 可以设置,但是你如何从那里测试?
    • 我的处理方式:在我的测试期间,不是发送电子邮件,而是将它们作为文件保存在临时文件夹中。我认为您不应该在每次运行测试时都发送电子邮件。但是,最好将电子邮件的内容保存在您可以查看的地方。另一个技巧是在文件中添加电子邮件的标题,从 foo@bar.com 到 bob@bob.com 主题“Hello World”等。
    • 我最终在 docker 中使用 mailcatch 设置了我的。花了很长时间
    【解决方案5】:

    如果有人使用 docker 作为开发环境,我最终会通过以下方式解决这个问题:

    设置

    .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),
    

    (由于某种原因我与此环境变量发生冲突)

    继续……

    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>
    

    测试

    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');
        }
    }
    

    (这是从我自己的代码中复制和编辑的,所以第一次可能无法正常工作)

    (来源:https://stackoverflow.com/a/52177526/563247

    【讨论】:

      【解决方案6】:

      我认为检查日志不是好方法。

      您可能想看看如何模拟 Mail 门面并检查它是否接收到带有一些参数的调用。

      【讨论】:

        【解决方案7】:

        如果你在 laravel 中使用 Notifcations,你可以像下面那样做

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

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

        【讨论】:

          【解决方案8】:

          如果您想测试电子邮件周围的所有内容,请使用

          Mail::fake()
          

          但如果您想测试您的Illuminate\Mail\Mailableblade,请按照此示例进行操作。比如说,您想测试一封关于某项付款的提醒电子邮件,其中电子邮件文本应包含名为“valorant”的产品和一些以“美元”为单位的价格。

           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);
          }
          

          这里的重要部分是-&gt;render() - 这就是你如何让Illuminate\Mail\Mailable 运行build() 函数并处理blade

          另一个重要的事情是auth()-&gt;logout(); - 因为通常电子邮件是在后台环境中运行的队列中处理的。这个环境没有用户,也没有请求,没有URL,也没有IP……

          因此,您必须确保在与生产环境类似的环境中在单元测试中呈现电子邮件。

          【讨论】:

            猜你喜欢
            • 2016-11-06
            • 1970-01-01
            • 2019-08-01
            • 1970-01-01
            • 2018-01-27
            • 2019-01-13
            • 2016-08-26
            • 2020-09-03
            • 2017-10-26
            相关资源
            最近更新 更多