使用 Mock 对 Laravel 5 Mail 进行单元测试

Posted

技术标签:

【中文标题】使用 Mock 对 Laravel 5 Mail 进行单元测试【英文标题】:Unittesting Laravel 5 Mail using Mock 【发布时间】:2015-09-16 04:55:00 【问题描述】:

有没有办法在 Laravel 5 中测试 Mail?尝试了我在互联网上看到的唯一合法的模拟示例,但它似乎只适用于 Laravel 4。下面的当前代码。

    $mock = Mockery::mock('Swift_Mailer');
    $this->app['mailer']->setSwiftMailer($mock);

    ...some more codes here...

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

这是 ApiClient.php 的内容,最后一行是第 155 行,在堆栈跟踪中指示。

Mail::queue('emails.error', [
                    'error_message' => $error_message,
                    'request' => $request,
                    'stack_trace' => $stack_trace
                ], function ($message) use ($error_message) 
                    $message->to(env('MAIL_TO_EMAIL'), env('MAIL_TO_NAME'))->subject("[Project Error] " . $error_message);
                );

下面是堆栈跟踪

Method Mockery_0__vendor_Swift_Mailer::getTransport() does not exist on this mock object
 /Users/BON/WebServer/project/vendor/laravel/framework/src/Illuminate/Mail/Mailer.php:285
 /Users/BON/WebServer/project/vendor/laravel/framework/src/Illuminate/Mail/Mailer.php:285
 /Users/BON/WebServer/project/vendor/laravel/framework/src/Illuminate/Mail/Mailer.php:150
 /Users/BON/WebServer/project/vendor/laravel/framework/src/Illuminate/Mail/Mailer.php:255
 /Users/BON/WebServer/project/vendor/laravel/framework/src/Illuminate/Queue/Jobs/Job.php:126
 /Users/BON/WebServer/project/vendor/laravel/framework/src/Illuminate/Queue/Jobs/SyncJob.php:42
 /Users/BON/WebServer/project/vendor/laravel/framework/src/Illuminate/Queue/SyncQueue.php:25
 /Users/BON/WebServer/project/vendor/laravel/framework/src/Illuminate/Mail/Mailer.php:184
 /Users/BON/WebServer/project/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php:216
 /Users/BON/WebServer/project/app/Libraries/ApiClient.php:155
 /Users/BON/WebServer/project/app/Libraries/ApiClient.php:155
 /Users/BON/WebServer/project/app/Libraries/ApiClient.php:174
 /Users/BON/WebServer/project/tests/unit_tests/ApiClientUnitTest.php:43

另外,添加use Mockery;会出现以下错误。

PHP Warning:  The use statement with non-compound name 'Mockery' has no effect in /Users/BON/WebServer/project/tests/unit_tests/ApiClientUnitTest.php on line 9

这让我沮丧了好几个小时,以至于我已经在这里询问了 SO。奇怪的是,当 Laravel 决定升级到版本 5 时,在单元测试时没有直接支持测试邮件。

【问题讨论】:

【参考方案1】:

花了我一个下午的大部分时间,但这终于奏效了——我传入了一个闭包并给了它一个 Mockery 对象

正在测试的代码:

$subject = "The subject";

Mail::send('emails.emailTemplate', ['user' => $user ], 
function( $mail ) use ($user, $subject)
    $mail   -> to( $user -> email)
            -> subject( $subject );                 
);

有效的测试:

$subject = "The subject";
$user = factory(App\Models\User::class) -> create();

Mail::shouldReceive('send') -> once() -> with(
        'emails.emailTemplate',
        m::on( function( $data )
            $this -> assertArrayHasKey( 'user', $data );
            return true; 
        ),
        m::on( function(\Closure $closure) use ($user, $subject)
            $mock = m::mock('Illuminate\Mailer\Message');
            $mock -> shouldReceive('to') -> once() -> with( $user -> email )
                  -> andReturn( $mock ); //simulate the chaining
            $mock -> shouldReceive('subject') -> once() -> with($subject);
            $closure($mock);
            return true;
        )
    );

【讨论】:

感谢您发布此答案。我今天早上的大部分时间都花在了这个上面。 谢谢。对于m::on,我使用了\Mockery::on【参考方案2】:

刚刚从 Laravel 5 文档中发现 Facades 的处理方式不同,并且有自己的测试方式。由于我使用的是 Mail Facade,因此我从 Laravel 5 文档页面中生成的微薄信息中做了一些实验。这是我使用的代码

    // Mock the Mail Facade and assert that it receives a Mail::queue()
    // with [whatever info you wish to check is passed. in my case, they're error contents]
    Mail::shouldReceive('queue')->once()
        ->andReturnUsing(function($view, $view_params) 
            $this->assertNotEmpty($view_params['error_message']);
            $this->assertNotEmpty($view_params['request']);
            $this->assertNotEmpty($view_params['stack_trace']);
        );

【讨论】:

【参考方案3】:
  Mail::shouldReceive('to')->once()->with('user email')
     ->andReturnUsing( function ($errorPasedToMailable) 
       // here you are passing whatever type (array|model) to your Mailable
       // in my case this is ['errorMessage' => 'some error']
      $this->assertNotEmpty($errorPasedToMailable['errorMessage']);
       // here result is your expected error message
      $this->assertEquals($result, $errorPasedToMailable['errorMessage'])

);

【讨论】:

非常好的测试电子邮件发送的方法。

以上是关于使用 Mock 对 Laravel 5 Mail 进行单元测试的主要内容,如果未能解决你的问题,请参考以下文章

升级到 Laravel 5.3 - Mail::send 现在从 Mandrill 返回 null

Laravel5.5 邮件驱动使用 SMTP 驱动实现邮件发送

未捕获的错误:将 laravel 5.8 升级到 8 后调用未定义的函数 Illuminate\Mail\TransportManager()

使用 gmail 发送 Laravel 5.5 邮件 [重复]

Laravel 5:发送电子邮件

无法在 laravel 5.2 中发送没有发件人地址的消息我已经设置了 .env 和 mail.php