PHPUnit模拟类,已命名静态构造函数

时间:2015-10-28 11:20:11

标签: php unit-testing mocking dependencies phpunit

鉴于我有一个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类使用命名的静态构造函数,addFruit()类类型的FruitSalad方法提示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属性公开一个setter 。

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

2 个答案:

答案 0 :(得分:1)

PHPUnit用于支持模拟静态方法,但是从PHPUnit 4.0开始,它被省略了。我在这里看到四个选项:

<强> 1。不要嘲笑这个方法

你可以调用方法并使用它的逻辑,虽然你也可以测试静态方法,并且通常在编写单元测试时应该避免这样做。

<强> 2。改变班级

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

第3。使用间谍课

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

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

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版本来使用此方法。也不是首选方式。

<强>结论

我没有看到模拟静态方法的简洁方法,并且因为4.0中的原因删除了::staticExpects()

答案 1 :(得分:0)

  

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

你做不到。通过使用模拟框架创建模拟。

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

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