如何使 Mockery 重载选项在 Laravel 5 上正常工作

Posted

技术标签:

【中文标题】如何使 Mockery 重载选项在 Laravel 5 上正常工作【英文标题】:How to make Mockery overload option work properly on Laravel 5 【发布时间】:2021-08-22 09:09:32 【问题描述】:

我正在尝试在 Laravel 5 上使用 Mockery 库的 overload 选项。

我现在的环境:

Laravel 5 嘲讽 1.0 phpUnit 7.5

我写了这个测试用例:

namespace Tests\Unit;

use Mockery;
use Tests\TestCase;

/**
 * @runTestsInSeparateProcesses
 * @preserveGlobalState disabled
 */
class RenewSignatureTest extends TestCase

    public function testHandle()
    
        $mock = Mockery::mock('overload:App\FooClass');

        $mock->shouldReceive('callBar')
            ->times(2);
    

根据documentation,这个测试应该失败,但不管我做什么,测试永远不会失败!它总是导致:

Time: 304 ms, Memory: 19.53 MB

OK (1 test, 1 assertion)

如果我删除 overload: 选项,测试将失败。所以我假设我没有按预期使用库的方法。

新测试:

namespace Tests\Unit;

use Mockery;
use Tests\TestCase;

/**
 * @runTestsInSeparateProcesses
 * @preserveGlobalState disabled
 */
class RenewSignatureTest extends TestCase

    public function testHandle()
    
        $mock = Mockery::mock('App\FooClass');

        $mock->shouldReceive('callBar')
            ->times(2);
    

结果:

Mockery\Exception\InvalidCountException: Method callBar(<Any Arguments>) from Mockery_0__App_FooClass should be called exactly 2 times but called 0 times.

我做错了吗?有谁知道如何正确使用这个选项?

【问题讨论】:

【参考方案1】:

阅读页面,我认为这是您正在寻找的错误:

当 Mockery 重载一个类时,由于 PHP 处理文件的方式, 不能包含重载的类文件,否则 Mockery 将 抛出“类已存在”异常。这是自动加载的地方 开始工作,让我们的工作轻松很多。

如果您从测试中删除这两行,则会导致您正在寻找的错误,它们被添加到第二个代码示例和手册页上的第三个示例中的代码中:

 * @runTestsInSeparateProcesses
 * @preserveGlobalState disabled

这意味着您的代码不会利用 psr4 自动加载器或任何现有的自动加载器,并且会以牺牲速度为代价为您创建一个新的自动加载器,因为它不会使用您转储的类映射并且必须构建它从头开始。

如果你去掉上面的两行,你会得到预期的错误,因为它会尝试用同名的类重载你的类。该类已经被自动加载,因此您会遇到致命错误。

因此,如果您想阻止对 callBar 的调用并返回 void,这就是您的代码将执行的操作,这是正确的。

删除overload 将意味着您的模拟不再有效,因为您必须将其传递给构造函数才能使其工作。

更新:

通过您的更新,我可以得出结论,您的代码必须运行两次模拟的 callBar 方法(不是实际的 callBar 方法),而您的模拟 fooBar 类使用 overload。当被模拟的方法被调用时,真正的callBar 方法内部实际上并没有发生任何事情,它只是注册它被调用了。例如,如果您期望它一次,请编写 shouldReceive(1),然后修复您的测试运行的代码。

当您删除 overload 时,不会发生全局注入,因此您的模拟永远不会被注入。但是,您在模拟类上的模拟 callBar 方法仍期望运行两次,因此您会收到错误消息。您需要从测试中完全删除 2 个模拟代码行。

保留 @ 语句,因为它有助于防止上述 psr4 错误。

【讨论】:

好的。我不知道我理解正确,但是,我的问题是我在两种情况下拥有完全相同的类:1-如果放置重载选项,则测试通过(这不应该发生,因为我明确表示 fooBar 需要被调用 2 次) 2-如果我删除重载选项,测试失败,说 Mockery_0__App_FooClass 应该被调用两次。为什么会发生这种情况? PS:我知道我没有那么明确。对此我很抱歉。我更新了问题。 PS2:“为什么会发生这种情况”的意思是“为什么我在两个类中没有相同的结果?” PS3:请记住我正在使用 Laravel 的 TestCase。 Wich 意味着存在隐式方法,例如框架实现的 setUp 和 tearDown 。如果你愿意,我可以在这里发布这些方法。 PS4:即使我删除那些“@ cmets”并单独运行此测试,我也会得到相同的结果。【参考方案2】:

这不是测试的方式,你永远不应该使用overload: 选项...... Laravel 可以帮助你做很多事情,不要再次创建***或尝试......

为什么需要使用@runTestsInSeparateProcesses@preserveGlobalState disabled


假设你的测试是这样的:

namespace Tests\Unit;

use App\FooClass;
use App\Service\ServiceForTesting;
use Mockery;
use Tests\TestCase;

class RenewSignatureTest extends TestCase

    public function testHandle()
    
        $mock = Mockery::mock(FooClass::class);

        $mock->shouldReceive('callBar')
            ->times(2);

        $myClass = app(ServiceForTesting::class);
        $myClass->run($mock);
        $myClass->run($mock);
    

你应该有这样的代码:

namespace App;

class FooClass

    public function callBar()
    
        return 'Works !!!';
    

现在,假设你有一个独立的类(没有控制器或类似的,你在一个单元测试中),你应该有这样的东西:

namespace App\Service;

use App\FooClass;

class ServiceForTesting

    public function run(FooClass $class)
    
        return $class->callBar();
    

【讨论】:

以上是关于如何使 Mockery 重载选项在 Laravel 5 上正常工作的主要内容,如果未能解决你的问题,请参考以下文章

Laravel Mockery 集成测试

Laravel - 如何使用 mockery 对更新用户数据的中间件进行单元测试

Mockery 中的重载和别名有啥区别?

在 Laravel 4 中使用 Mockery 模拟自定义类

Laravel 5 - 使用 Mockery 模拟 Eloquent 模型

这个模拟对象上不存在方法 - Laravel,Mockery