【问题标题】:Mock should be called exactly 1 times but called 0 timesMock 应该被准确地调用 1 次但被调用 0 次
【发布时间】:2016-01-09 07:09:11
【问题描述】:

我对 Laravel 5 和 PHPUnit 有奇怪的问题。当我尝试模拟 Laravel 的门面(例如 Auth、View、Mail)时,我总是得到这个异常:

Mockery\Exception\InvalidCountException: 应该调用来自 Mockery_0_Illuminate_Mail_Mailer 的方法 send("emails.register", array('user'=>'object(MCC\Models\Users\User)',), object(Closure)) 正好 1 次,但调用了 0 次。

"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) 关闭是因为我想了解有关返回值的详细信息($message->getTo() 的外观)。

我的 TestCase 类:

<?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、Stackoverflow 的资源,大多数人都提到过

$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

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

Mockery 默认是一个存根库,而不是一个模拟库(因为它的名字而令人困惑)。

这意味着 ->shouldReceive(...) 默认为“零次或多次”。当使用 ->once() 时,你说它应该被调用零次或一次,但不能更多。这意味着它总会过去。

当你想断言它被调用一次时,你可以使用 ->atLeast()->times(1) (一次或多次)或 ->times(1) (恰好一次)

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

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

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

Laravel 框架(来自 composer):"laravel/framework": "5.1.*"

【问题讨论】:

    标签: php laravel unit-testing mockery


    【解决方案1】:

    查看您的测试用例:

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

    【讨论】:

      【解决方案2】:

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

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

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

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

      【讨论】:

      • Laravel 的 tearDown() 内置方法有 Mockery::close(); ,但它仍然没有帮助。继承父方法tearDown() 并将函数添加到开头(或结尾)也无济于事。从 Mockery 的文档中,我将监听器放在我的 phpunit.xml 中,但它仍然无法正常工作。还是一样的例外。
      • 您是否尝试过关闭嘲笑而不继承父关闭?
      • 是的,有多种组合。我将Mockery::close() 放在方法的末尾,在TestCase 类中自定义tearDown(),在测试类中自定义tearDown(),当然还要混合这些组合。还是一样的例外。
      猜你喜欢
      • 2017-01-03
      • 2020-03-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-14
      • 2020-09-15
      • 1970-01-01
      • 2021-08-15
      相关资源
      最近更新 更多