14

Is there a way to create a mock class with PHPUnit which I can then create a new instance of by using its class name?

I have an interface which defines two methods. Something like:

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

I then have another class which accepts a class name, creates an instance of that class, checks if it is an instance of what it expects (FooInterface) and then calls two methods on that class to get some information.

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();
    }
}

I know how to mock an object just fine. The problem is, since this class accepts a class name, not an object (it is a part of a Manager which creates instances of the given class as needed), I can't use a normal mock object.

I tried to make a mock object then use that class name. It seems to create the object just fine, and even seems to have the functions I mocked out. However, it doesn't seem to follow the will($this->returnValue('myValue')) portion I set up later.

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());
}

So, it does have the functions which were mocked, but they all return null.

Any ideas?

4

2 回答 2

14

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

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

也就是说,你想做的事情可以用padraic的优秀 mocking library mockery来完成!您需要的是一个“实例模拟”:

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

您可以在该模拟上定义您的期望,该模拟将在实例化后立即转移到真实对象。

将 mockery 与 phpUnit 集成起来很容易,并且在项目的自述文件中得到了很好的解释。

顺便说一句。每个单元测试最好只做出一个断言!

于 2013-01-31T11:21:10.977 回答
0

方法getAgetB返回 null 因为您没有指定它们应该返回什么。$foo您确实通过调用一些方法为抽象指定了这一点。没有办法解决这个问题。

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

于 2013-01-31T07:13:15.800 回答