Unittesting Laravel 5 Mail using Mock
Asked Answered
B

3

5

Is there a way to test Mail in Laravel 5? tried the only legit Mock example I see on the internet but it seems it only works on Laravel 4. current code below.

    $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('[email protected]', $msg->getTo());
             $this->assertContains('Some string', $msg->getBody());
         });

this is the contents of ApiClient.php, the last line is line 155, which is indicated in the stack trace.

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

below is the stack trace

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

in addition, adding use Mockery; gets the following error.

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

This frustrates me for hours to the point that i'm already asking here on SO. It's just weird that Laravel doesn't have a direct support for testing mails when unittesting when they decided to upgrade to version 5.

Burthen answered 29/6, 2015 at 16:12 Comment(0)
S
10

Cost me the better part of an afternoon but this is finally what worked - I passed in a Closure and gave it a Mockery object

Code being tested:

$subject = "The subject";

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

Test that worked:

$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;
        })
    );
Stringent answered 30/6, 2015 at 10:29 Comment(1)
Thank you for posting this answer. I spent the better half of this morning on this.Towline
B
5

Just discovered from the Laravel 5 documentation that Facades are treated differently and have their own ways to be tested. Since i'm using a Mail Facade, i did a bit of experimentation from the meager info produced in the Laravel 5 documentation page. so here's the code i used

    // 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']);
        });
Burthen answered 30/6, 2015 at 3:48 Comment(0)
G
0
  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'])

});
Gustation answered 8/8, 2018 at 15:47 Comment(1)
Very nice way of testing sending of emails.Gustation

© 2022 - 2024 — McMap. All rights reserved.