如何使用 Mockery 模拟构造函数

Posted

技术标签:

【中文标题】如何使用 Mockery 模拟构造函数【英文标题】:How to mock a constructor with Mockery 【发布时间】:2015-08-15 00:14:53 【问题描述】:

我需要测试一下,代码会创建一个具有某些参数的类的新实例:

$bar = new ProgressBar($output, $size);

我尝试创建一个别名模拟并为__construct 方法设置一个期望,但它不起作用:

$progressBar = \Mockery::mock('alias:' . ProgressBar::class);
$progressBar->shouldReceive('__construct')
    ->with(\Mockery::type(OutputInterface::class), 3)
    ->once();

这个期望永远不会满足:

嘲弄\异常\无效计数异常:方法 __construct(object(Mockery\Matcher\Type), 3) 来自 Symfony\Component\Console\Helper\ProgressBar 应该被准确地调用 1 次,但调用了 0 次。

您知道如何使用 Mockery 进行测试吗?

【问题讨论】:

你的构造函数是否包含业务逻辑?如果是这样,那么它可能不应该,因为构造函数只应该真正关心将对象初始化为有效的初始状态。这通常只是将参数分配给成员变量,这很简单,不需要测试。 我不太确定是否正确理解了您的问题。你想创建一个 Mock 但不知道如何传递构造函数参数?还是您真的要检查是否调用了构造函数方法?因为那将是非常非常糟糕的做法 - 这不是它的工作原理。或者您正在测试一种创建 ProgressBar 对象的方法?然后你应该重构它,以便将 PrograssBar 对象作为参数传递给该方法或作为包含该方法的类的构造函数参数(依赖注入)。 我正在为 Symfony 控制台编写一个简单的命令。我想测试在执行命令期间是否初始化了具有正确单位数的 ProgressBar。 @VaclavSir 好的,这不太可能,例如php单元。有一种叫做“方面模拟”的东西允许这样的场景,但仅仅因为你可以并不意味着你应该。正如我上面提到的,请阅读“依赖注入”。执行此操作的 OOP 方法是将 PrograssBar 对象传递给方法,而不是在方法中创建它。 不,不会,DI 方式是注入工厂。然而,直接实例化 ProgressBar 是一种常见的做法,工厂除了return new ProgressBar(...func_get_args()) 不会做任何事情。所以我想知道这种做法是否可以用 Mockery 来测试。我会看看 AspectMock。 【参考方案1】:

你不能模拟构造函数。相反,您需要稍微修改您的生产代码。我可以从描述中猜到你有这样的东西:

class Foo 
    public function bar()
        $bar = new ProgressBar($output, $size);
    


class ProgressBar
    public function __construct($output, $size)
        $this->output = $output;
        $this->size = $size;
    

这不是世界上最好的代码,因为我们有耦合依赖。 (例如,如果 ProgressBar 是值对象,这完全可以)。

首先,您应该将ProgressBarFoo 分开测试。因为那时你测试Foo 你不需要关心ProgressBar 是如何工作的。你知道它有效,你对此进行了测试。

但是,如果您仍然想测试它的实例化(出于任何原因),这里有两种方法。对于这两种方式,您都需要提取 new ProggresBar

class Foo 
    public function bar()
        $bar = $this->getBar($output, $size);
    

    public function getBar($output, $size)
        return new ProgressBar($output, $size)
    

方式一:

class FooTest
    public function test()
        $foo = new Foo();
        $this->assertInstanceOf(ProgressBar::class, $foo->getBar(\Mockery::type(OutputInterface::class), 3));
    

方式二:

class FooTest
    public function test()
        $mock = \Mockery::mock(Foo::class)->makePartial();
        $mock->shouldReceive('getBar')
            ->with(\Mockery::type(OutputInterface::class), 3)
            ->once();
    

测试愉快!

【讨论】:

以上是关于如何使用 Mockery 模拟构造函数的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 mockery 来模拟没有命名空间的全局类?

如何使用 mockery 来模拟 httpclient

如何使用 Mockery 模拟嵌套对象,如 GuzzleHttp 请求

如何使用 Mockery 在模拟方法的第 N 次调用中引发异常

Mockery 和 Laravel 构造函数注入

返回参数被发送到 Mockery 模拟函数?