使用 Mockery 和硬依赖设置多个返回值

Posted

技术标签:

【中文标题】使用 Mockery 和硬依赖设置多个返回值【英文标题】:Set up multiple return values with Mockery and hard dependency 【发布时间】:2021-09-03 07:43:35 【问题描述】:

考虑以下类重现我的真实用例:


namespace App;

class Foo

    public function greeting(): string
    
        return (new Greeting())->welcome();
    


namespace App;

class Greeting

    public function welcome(): string
    
        return 'hello world';
    


考虑到这个使用Mockery(版本1.3.4)的测试类重载Foo类(new Greeting())中的hard dependency:


namespace Test;

use App\Foo;
use App\Greeting;
use Mockery;
use phpUnit\Framework\TestCase;

class FooTest extends TestCase

    public function testGreeting(): void
    
        $greetingMock = Mockery::mock('overload:' . Greeting::class);
        $greetingMock->shouldReceive('get')
            ->twice()
            ->andReturn(
                'foo',
                'bar'
            );

        $foo = new Foo();

        echo $foo->greeting() . PHP_EOL;
        echo $foo->greeting() . PHP_EOL;
    

我想知道为什么输出是:

foo
foo

而不是:

foo
bar

我是否误读了有关 andReturn 方法的文档?:

可以为多个返回值设置期望。通过提供一系列返回值,我们告诉 Mockery 在每次后续调用该方法时要返回什么值

【问题讨论】:

【参考方案1】:

感谢您的提问。

你可能会因为一次应用两个(新的?)东西然后落在两者之间而陷入困境。

您实际上应该看到您的测试失败了。然后带着失败去找出任何与预期不相符的问题。

这确实需要根据您对测试框架和正在使用的模拟库的期望逐步编写测试,然后才能再次使用它来制定您的期望。否则事情很容易变得不成比例,然后很容易变成“只见树木不见森林”的情况。

return (new Greeting())->welcome();

然后,您也可以自己回答这个问题,无论您是否误读了文档中的某些内容。这就是测试的本质,也是您应该如何让自己能够使用测试框架的方式——否则它不值得满足您的需求。


在这个介绍性的、更一般的评论旁边,您的代码中有多个错误会妨碍有效的模拟和测试。让我们来看看,也许通过一些潜在的误解也可以消除:

$greetingMock->shouldReceive('get')

方法名称是welcome 而不是get。你应该已经看到了一个错误:

Mockery\Exception\BadMethodCallException : 方法 App\Greeting::welcome() 在这个模拟对象上不存在

通过一个工作示例,您可能会了解到配置 Mockery 的预期默认情况下是无效的。通过将twice() 转换为never() 并看到调用$foo->greeting() 确实不会使测试失败,这很容易找出。

同样,这可能只是问题中的一个不完整示例,但那时我仍然不知道。因此,请仔细检查您对“在线”的期望,否则您将不知道twice() 是否有效。

为什么我把重点放在twice()上?

嗯,因为当你分享你的问题时,你已经发现了

andReturn(A, B)

仅返回 A,根据文档,这意味着这是第一次调用该方法。

启用期望后,这将直接显示:

Mockery\Exception\InvalidCountException : App\Greeting 中的方法 welcome() 应该被准确地调用 2 次,但被调用了 1 次。

所以,也许总体上的期望还没有达到您的预期?所以也许仔细检查你的声明是否有效。见the note at the very top of Mockery Expectation Declarations

启用期望后,很明显该方法不会被调用两次,而是只调用一次,因此它正确适用于 addReturn 仅在第一次调用时返回。

Mockery\Exception\InvalidCountException : App\Greeting 中的方法 welcome() 应该被准确地调用 2 次,但被调用了 1 次。

现在回到您在原始问题中的提问:

我是否误读了有关 andReturn 方法的文档?

不,你读的很好。

只是您期望该方法会被调用两次是错误的。

它只被调用一次。这也写在您的示例代码中:

return (new Greeting())->welcome();

在每个(新的)Greeting 中,welcome 被调用一次,而不是两次。

例如,通过使用 never 而不是 twice 来强制测试首先失败是一种快速摆脱“非工作测试”的粗糙边缘的简单技术。

您小时候可能在玩耍时就知道了这一点:

如果某些东西有效,请尝试打破它。

在测试中,首先打破事物。您实际上想展示“工作”的界限是什么。


因此,如果测试失败并且不清楚失败的原因,则该测试没有多大用处。

对于单元测试,专门说测试也写得不好,因为单个测试失败应该只有一个原因,所以当它变红时就清楚问题是什么。

像所讨论的测试这样的测试还远远没有准备好。制定了一些期望,但从未断言。

在 *** 上将一个简化且有效的示例放入问题中可能已经对此有所帮助。

相反,您在测试方法中创建了中间代码来测试不同的东西,期望就像 cmets 不会自动断言一样。

下一次,首先将您想要测试的内容隔离在一个更小的、自己的测试方法中。然后先完成该测试,然后继续使用其他测试方法。

记录你自己的理解,包括。你的先决条件有一个测试工作。它是你的测试。而且更容易从小处着手,然后进行更改,而不是预先编写整个测试。

【讨论】:

感谢您指出所犯的几个错误,并为我提供正确的思考过程。我非常感谢这个清晰而完善的答案!

以上是关于使用 Mockery 和硬依赖设置多个返回值的主要内容,如果未能解决你的问题,请参考以下文章

Mockery 的 andReturn() 执行方法,而不是模拟返回值

Laravel Mockery:模拟返回逻辑

嘲弄模拟不返回指定值

Gmock当返回多个不同值应该怎么用willrepeatedly

如何使用 mockery 来模拟 httpclient

你如何在 GoLang 的模拟中模拟错误返回值?