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

Set up multiple return values with Mockery and hard dependency

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


namespace App;

class Foo
{
    public function greeting(): string
    {
        return (new Greeting())->welcome();
    }
}

namespace App;

class Greeting
{
    public function welcome(): string
    {
        return 'hello world';
    }
}

并考虑到此测试 class 在 Foo class(new Greeting())中使用 Mockery (version 1.3.4) to overload the 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 方法的文档?:

It is possible to set up expectation for multiple return values. By providing a sequence of return values, we tell Mockery what value to return on every subsequent call to the method

感谢您的提问。

您可能会因为同时应用两个(新的?)事物然后陷入两者之间而陷入困境。

您实际上应该看到您的测试失败了。然后带着失败一起去寻找与期望有什么问题。

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

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

然后您自己也可以回答您是否误读了文档中的内容。这就是测试的本质,以及您应该如何使自己能够使用测试框架 - 否则它不符合您的需求。


除了这个介绍性的、更笼统的评论之外,您的代码中还有多个错误阻碍了有效的模拟和测试。来看看吧,也许通过一些潜在的误会也可以消除:

$greetingMock->shouldReceive('get')

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

Mockery\Exception\BadMethodCallException : Method App\Greeting::welcome() does not exist on this mock object

通过一个工作示例,您可能会了解到配置的 Mockery 的期望默认情况下无效。通过将 twice() 变成 never() 并看到调用 $foo->greeting() 确实 not[=101 可以很容易地找到=] 使测试失败。

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

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

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

andReturn(A, B)

只有returns A,根据文档说明这是第一次调用该方法。

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

Mockery\Exception\InvalidCountException : Method welcome() from App\Greeting should be called exactly 2 times but called 1 times.

所以也许一般的期望还没有达到你的目的?因此,也许仔细检查您的声明是否有效。参见 the note at the very top of Mockery Expectation Declarations

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

Mockery\Exception\InvalidCountException : Method welcome() from App\Greeting should be called exactly 2 times but called 1 times.

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

Am I misreading the documentation about the andReturn method?

不,你读得很好。

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

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

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

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

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

你小时候玩的时候可能已经学会了:

如果有用,就尝试破解它。

在测试中,先打破东西。你实际上想展示“工作”的界限是什么。


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

单元测试专门说测试也写的不好,因为一个测试失败应该只有一个原因,等红了就知道是什么问题了。

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

在 Whosebug 上的问题中放入一个简化的工作示例可能已经对此有所帮助。

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

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

记录您自己的理解,包括。您进行测试的先决条件。这是你的考验。从小处着手然后进行更改比预先编写整个测试更容易。