PHPUnit在实例化之后将类的实例转换为模拟

时间:2013-01-30 22:36:56

标签: php unit-testing mocking phpunit

有没有办法用PHPUnit创建一个mock类,然后我可以使用它的类名创建一个新的实例?

我有一个定义两个方法的接口。类似的东西:

interface FooInterface {
    function getA();
    function getB();
}

然后我有另一个接受类名的类,创建该类的实例,检查它是否是它所期望的实例(FooInterface),然后在该类上调用两个方法来获取一些信息。

class FooInfo {
    protected $a;
    protected $b;

    public function __construct($fooClass) {
        $foo = new $fooClass;

        if (!($foo instanceof FooInterface)) {
            throw new \Exception();
        }

        $this->a = $foo->getA();
        $this->b = $foo->getB();
    }
}

我知道如何模拟对象就好了。问题是,因为这个类接受一个类名,而不是一个对象(它是一个Manager的一部分,根据需要创建给定类的实例),我不能使用普通的模拟对象。

我尝试制作一个模拟对象然后使用该类名。它似乎创造了对象很好,甚至似乎有我嘲笑的功能。但是,它似乎不遵循我稍后设置的意愿($ this-> returnValue('myValue'))部分。

public function testConstruct()
{
    $foo = $this->getMockForAbstractClass('Foo', array('getA', 'getB'));
    $foo->expects($this->any())->method->('getA')->will($this->returnValue('a'));
    $foo->expects($this->any())->method->('getB')->will($this->returnValue('b'));

    $copyClass = get_class($foo);
    $copy = new $copyClass();

    // Passes
    $this->assertTrue(method_exists($copy, 'getA');

    // Fails, $copy->getA() returns null.
    $this->assertEquals($copy->getA(), $foo->getA());
}

因此,它确实具有被模拟的函数,但它们都返回null。

有什么想法吗?

2 个答案:

答案 0 :(得分:14)

在类的构造函数中使用new关键字是一个相当糟糕的习惯,完全出于您现在遇到的原因,即使您的灵活用例也是如此。

您的测试无效,因为您创建的模拟将永远不会被使用,因为您的类将始终创建注入的类名的实际实例。

那就是说,你想做的事情可以用padraic优秀的模拟库mockery来完成! 你需要的是'实例模拟':

$mock = \Mockery::mock('overload:MyNamespace\MyClass');

您可以定义您对该模拟的期望,一旦实例化,它将被转移到真实对象。

将mockery与phpUnit集成在一起很简单,并在项目自述文件中得到了很好的解释。

顺便说一下。每个单元测试应该最佳地只做一个断言!

答案 1 :(得分:0)

方法getAgetB返回null,因为您尚未指定应返回的内容。您确实通过调用某些方法为抽象$foo指定了该值。没有办法解决这个问题。

由于测试函数很难(如果不是不可能),我会重写代码本身。如果构造函数需要类实例而不是类名,那么测试将很容易。如果您还必须接受字符串,则可以编写几行来检查字符串输入,并在提供字符串时创建一个类。