Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHPUnit turning an instance of a class into a mock after instantiation

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?

like image 453
samanime Avatar asked Jan 30 '13 22:01

samanime


1 Answers

To use the new keyword in the constructor of a class is a rather bad habit exactly for the reasons you're experiencing now, even given your flexible use case.

Your test will not work because the mock you create will never be used, since your class will always create a real instance of the injected classname.

That said, what you want to do can be done with padraic's excellent mocking library mockery! What you need is an 'instance mock':

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

You can define your expectations on that mock which will be transferred to the real object, as soon as it is instantiated.

Integrating mockery with phpUnit is easy and well explained in the readme of the project.

And btw. each unit test should optimally make one assertion only!

like image 130
markus Avatar answered Oct 09 '22 03:10

markus