Mock 应该恰好被调用 1 次但被调用 0 次

Mock should be called exactly 1 times but called 0 times

我在使用 Laravel 5 和 PHPUnit 时遇到了奇怪的问题。当我尝试模拟 Laravel 的外观(例如 Auth、View、Mail)时,我总是得到这个异常:

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.

我对 "should be called exactly 1 times but called 0 times." 部分有疑问。这是我的测试代码:

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

我把 dd($message) 放在了闭包中,因为我想知道有关 return 值的详细信息(看起来如何 $message->getTo())。

我的测试用例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;
    }
}

我的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>

我从 Google、Whosebug 查了很多资料,大多数人都提到了

$this->app->instance('Illuminate\Mail\Mailer', $mockMailer)

但即使是这条指令也无济于事。关于这个问题的大部分问题都没有解决。我检查了已安装的扩展,我的 Laravel 是全新安装的(某些型号,某些路线,大约 20 次测试)。

我也试过像

这样的方法
->atLeast()
->times(1)

->atLeast()
->once()

但没有任何东西正常工作。也代替

Mail::shouldReceive('mail')

我用过

$mailMock = Mockery::mock('Illuminate\Mail\Mailer');
$mailMock->shouldReceive('mail)

但是这些方法还是不行。

控制台日志的其余部分:

/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

我还找到了一个很好的建议(Whosebug),但它不起作用。

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)

我的php版本:PHP 5.6.14-1+deb.sury.org~trusty+1 (cli)

我的阿帕奇:Server version: Apache/2.4.16 (Ubuntu)

嘲讽版(来自作曲家):"mockery/mockery": "0.9.*"

Laravel 框架(来自作曲家):"laravel/framework": "5.1.*"

要将 Mockery 与 PhpUnit 集成,您只需为包含以下内容的测试定义一个 tearDown() 方法:

public function tearDown() {
    \Mockery::close();
}

此静态调用清理当前测试使用的 Mockery 容器,以及运行您期望所需的任何验证任务。

您可以找到有关 Mockery 和 PhpUnit 集成的更多信息here

查看您的测试用例:

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

或者:

  • 创建用户调用 Mail 外观,在这种情况下,您在 调用该外观之前 您正在模拟它。

  • 您没有调用调用 Mail 外观的函数。

无论如何,Mail::shouldReceive('send') 应该 是测试用例中的最后一件事。

您遇到的错误是因为 Mail 期望在您调用 ::shouldRecieve() 之后 发生调用 ,但事实并非如此 - 测试用例结束,并且从未调用过 Mockery 实例。

您可以试试这样的方法:

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