I have weird problem with Laravel 5 and PHPUnit. When I try to mock Laravel's facades (e.g. Auth, View, Mail) I always get this exception:
Mockery\Exception\InvalidCountException: Method send("emails.register", array('user'=>'object(MCC\Models\Users\User)',), object(Closure)) from Mockery_0_Illuminate_Mail_Mailer should be called exactly 1 times but called 0 times.
I have a problem with "should be called exactly 1 times but called 0 times."
part. This is my test code:
public function testSendEmailToNewUserListener()
{
$user = factory(MCC\Models\Users\User::class)->create();
Mail::shouldReceive('send')
->with(
'emails.register',
['user' => $user],
function ($mail) use ($user) {
$mail->to($user->email, $user->name)
->subject('Thank you for registering an account.');
}
)
->times(1)
->andReturnUsing(function ($message) use ($user) {
dd($message);
$this->assertEquals('Thank you for registering an account.', $message->getSubject());
$this->assertEquals('mcc', $message->getTo());
$this->assertEquals(View::make('emails.register'), $message->getBody());
});
}
I put dd($message)
in closure because I want to know details about return value (how looks $message->getTo()
).
My TestCase class:
<?php
/**
* Provides default values for functional tests.
*
* Class TestCase
*/
abstract class TestCase extends Illuminate\Foundation\Testing\TestCase
{
/**
* The base URL to use while testing the application.
*
* @var string
*/
protected $baseUrl = 'http://004-mcc.dev';
/**
* Creates the application.
*
* @return \Illuminate\Foundation\Application
*/
public function createApplication()
{
$app = require __DIR__ . '/../bootstrap/app.php';
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
\Illuminate\Support\Facades\Mail::pretend(TRUE);
return $app;
}
}
My phpunit.xml:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="bootstrap/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false">
<testsuites>
<testsuite name="Application Test Suite">
<directory>./tests/</directory>
</testsuite>
<testsuite name="User">
<directory>./tests/UserRepository</directory>
</testsuite>
<testsuite name="User/Auth">
<directory>./tests/UserRepository/Auth</directory>
</testsuite>
<testsuite name="User/User">
<directory>./tests/UserRepository/User</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">app/</directory>
</whitelist>
</filter>
<php>
<env name="APP_ENV" value="local"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="QUEUE_DRIVER" value="sync"/>
</php>
</phpunit>
I checked many sources from Google, from Stackoverflow, most people mentioned about
$this->app->instance('Illuminate\Mail\Mailer', $mockMailer)
but even this instruction doesn't help. Most of questions about this problem are not solved. I checked installed extensions, my Laravel is fresh installed (some models, some routes, about 20 tests).
Also I tried methods like
->atLeast()
->times(1)
or
->atLeast()
->once()
but nothing is works properly. Also instead of
Mail::shouldReceive('mail')
I used
$mailMock = Mockery::mock('Illuminate\Mail\Mailer');
$mailMock->shouldReceive('mail)
but these methods still not work.
Rest of console log:
/home/grzgajda/programowanie/php/005mcc/vendor/mockery/mockery/library/Mockery/CountValidator/Exact.php:37
/home/grzgajda/programowanie/php/005mcc/vendor/mockery/mockery/library/Mockery/Expectation.php:271
/home/grzgajda/programowanie/php/005mcc/vendor/mockery/mockery/library/Mockery/ExpectationDirector.php:120
/home/grzgajda/programowanie/php/005mcc/vendor/mockery/mockery/library/Mockery/Container.php:297
/home/grzgajda/programowanie/php/005mcc/vendor/mockery/mockery/library/Mockery/Container.php:282
/home/grzgajda/programowanie/php/005mcc/vendor/mockery/mockery/library/Mockery.php:142
/home/grzgajda/programowanie/php/005mcc/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php:48
/home/grzgajda/.composer/vendor/phpunit/phpunit/src/TextUI/Command.php:148
/home/grzgajda/.composer/vendor/phpunit/phpunit/src/TextUI/Command.php:100
Also I found one great advice (there Stackoverflow) but it doesn't work.
Mockery is by default a stubbing library, not a mocking one (which is confusing because of its name).
That means that ->shouldReceive(...) by default is "zero or more times". When using ->once(), you say it should be called zero or one time, but not more. This means it'll always pass.
When you want to assert that it is called once, you can use ->atLeast()->times(1) (one or more times) or ->times(1) (exactly one time)
My php version: PHP 5.6.14-1+deb.sury.org~trusty+1 (cli)
My apache: Server version: Apache/2.4.16 (Ubuntu)
Mockery version (from composer): "mockery/mockery": "0.9.*"
Laravel framework (from composer): "laravel/framework": "5.1.*"
Looking at your test case:
public function testSendEmailToNewUserListener()
{
$user = factory(MCC\Models\Users\User::class)->create();
Mail::shouldReceive('send')
->with(
'emails.register',
['user' => $user],
function ($mail) use ($user) {
$mail->to($user->email, $user->name)
->subject('Thank you for registering an account.');
}
)
->times(1)
->andReturnUsing(function ($message) use ($user) {
dd($message);
$this->assertEquals('Thank you for registering an account.', $message->getSubject());
$this->assertEquals('mcc', $message->getTo());
$this->assertEquals(View::make('emails.register'), $message->getBody());
});
}
Either:
Mail
facade, in which case you're calling that facade before you're mocking it up.OR
Mail
facade.Either way, Mail::shouldReceive('send')
should not be the last thing in the test case.
The error you are getting is because Mail
is expecting the call to happen after you call ::shouldRecieve()
, but it's not - the test case ends, and the Mockery
instance has never been called.
You could try something like this instead:
public function testSendEmailToNewUserListener()
{
$testCase = $this;
Mail::shouldReceive('send')
->times(1)
->andReturnUsing(function ($message) use ($testCase) {
$testCase->assertEquals('Thank you for registering an account.', $message->getSubject());
$testCase->assertEquals('mcc', $message->getTo());
$testCase->assertEquals(View::make('emails.register'), $message->getBody());
});
$user = factory(MCC\Models\Users\User::class)->create();
}