【问题标题】:PHPUnit mock class that has named static constructor具有命名静态构造函数的 PHPUnit 模拟类
【发布时间】:2016-01-28 03:28:19
【问题描述】:

假设我有一个FruitSalad 类(被测系统):

class FruitSalad
{
    protected $fruits = [];

    public function addFruit(Fruit $fruit)
    {
        $this->fruits[] = $fruit;
        return $this;
    }
}

我有一个Fruit 类:

class Fruit
{
    public static function withName($name)
    {
        $instance = new MyDependencyClass();
        $instance->name = $name;
        return $instance;
    }
}

一个简单的示例,但是您可以看到 Fruit 类使用命名静态构造函数,FruitSalad 类类型上的 addFruit() 方法提示 Fruit 作为其预期参数。

在为addFruit() 编写测试时,我需要模拟Fruit 类。

function test_it_can_add_a_fruit_to_its_list_of_fruits()
{
    $fruit = $this->getMockBuilder('Fruit')
        ->disableOriginalConstructor()
        ->getMock();

    $this->fruitSalad->addFruit($fruit);
    // Do some assertion.
}

这创建了Fruit 类的简单模拟,但我想通过withName() 静态方法实例化它 - 而且我不想公开name 属性的设置器。

如何使用静态命名构造函数为Fruit 创建模拟?

【问题讨论】:

  • 你想模拟withName吗?如果它应该启动Fruit 对象,静态方法是错误的选择,请改用__construct。此外,withName 在您的示例中无论如何都没有被调用,所以不应该模拟该类就足够了,或者您稍后再调用该方法?
  • 我的示例中没有使用它,因为我需要一个模拟。我不想显式使用Fruit::withName('Apple'); 模拟需要调用该静态方法来返回对象。
  • 顺便说一句,您的静态“构造函数”有一个术语 - 工厂方法

标签: php unit-testing mocking dependencies phpunit


【解决方案1】:

PHPUnit 曾经支持模拟静态方法,但从 PHPUnit 4.0 起它被省略了。我在这里看到四个选项:

1.根本不要模拟该方法

您可以只调用该方法并使用它的逻辑,尽管如果您这样做,您也会测试静态方法,通常这是您在编写单元测试时应该避免的事情。

2。换班

问问自己这个方法是否真的需要是静态的,如果不需要,请更改类以正确测试它。在很多用例中,最好更改一些架构以编写正确的测试。

3.使用间谍类

Spy 类是扩展您通常会模拟的类的类,但会实现一些逻辑来测试类的配置或测试方法对另一个方法的依赖关系。在大多数情况下,可以通过适当地模拟类来避免这种情况。如果模拟还不够,间谍只是你的工作,在极少数情况下你真的需要它们。

但是,在这种情况下,可以使用间谍来覆盖静态方法作为解决方法:

class FruitSpy extends Fruit
{

    public static $return;

    public static $name;

    public static function withName($name) {
        $expected = self::$name;
        if($name == $expected) {
            return self::$return;
        } else {
            throw new \RuntimeException("FruitSpy::withName(): Parameter 0 was $name, $expected expected");
        }
    }

}

此示例检查正确的$name,如果正确,则返回您定义的返回值。你会在测试中这样使用它:

$fruitSpy = new FruitSpy();
$fruitSpy::$name = "Banana";
$fruitSpy::$return = new \stdClass();

$this->fruitSalad->addFruit($fruitSpy);

不完全是一个干净的解决方案,但我看到您是否绝对不想更改测试代码以外的其他代码的唯一方法。

同样,如果您需要做这样的事情,您应该考虑将静态方法更改为临时方法。

4.使用 PHPUni 3.*

您可以简单地使用已弃用的 PHPUnit 版本来使用此方法。也不是首选方式。

结论

我看不到模拟静态方法的干净方法,并且 ::staticExpects() 在 4.0 中出于某种原因被删除

【讨论】:

    【解决方案2】:

    如何使用静态命名构造函数为 Fruit 创建一个模拟?

    你不能。模拟是使用模拟框架创建的。

    无论如何,如何创建模拟并不重要,而是它们的行为方式无关紧要,因为它们在被测试的类之外。

    只需配置模拟,使其行为方式与使用Fruit::withName 创建的真实 Fruit 实例相同。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-15
      • 2010-09-21
      • 1970-01-01
      • 1970-01-01
      • 2011-11-13
      相关资源
      最近更新 更多