Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHPUnit Stubbing Class methods declared as "final"

I'm writing a unit test for a class method that calls another class's method using a mock, only the method that needs to be called is declared as final, so PHPUnit is unable to mock it. Is there a different approach I can take?

example:

class to be mocked

class Class_To_Mock
{
    final public function needsToBeCalled($options)
    {
        ...
    }
}

my test case

class MyTest extends PHPUnit_Framework_TestCase
{
    public function testDoSomething()
    {
        $mock = $this->getMock('Class_To_Mock', array('needsToBeCalled'));
        $mock->expects($this->once())
             ->method('needsToBeCalled')
             ->with($this->equalTo(array('option'));
    }
}

Edit: If using the solution provided by Mike B and you have a setter/getter for the object you're mocking that does type checking (to ensure the correct object was passed into the setter), you'll need to mock the getter on the class you're testing and have it return the other mock.

example:

class to be mocked

class Class_To_Mock
{
    final public function needsToBeCalled($options)
    {
        ...
    }
}

mock

class Class_To_MockMock
{
    public function needsToBeCalled($options)
    {
        ...
    }
}

class to be tested

class Class_To_Be_Tested
{
    public function setClassToMock(Class_To_Mock $classToMock)
    {
        ...
    }

    public function getClassToMock()
    {
        ...
    }

    public function doSomething()
    {
        $this->getClassToMock()
             ->needsToBeCalled(array('option'));
    }
}

my test case

class MyTest extends PHPUnit_Framework_TestCase
{
    public function testDoSomething()
    {
        $classToTest = $this->getMock('Class_To_Be_Tested', array('getClassToMock'));

        $mock = $this->getMock('Class_To_MockMock', array('needsToBeCalled'));

        $classToTest->expects($this->any())
                    ->method('getClassToMock')
                    ->will($this->returnValue($mock));

        $mock->expects($this->once())
             ->method('needsToBeCalled')
             ->with($this->equalTo(array('option'));

        $classToTest->doSomething();
    }
}
like image 885
rr. Avatar asked Jun 20 '10 16:06

rr.


2 Answers

I don't think PHPUnit supports stubbing/mocking of final methods. You may have to create your own stub for this situation and do some extension trickery:

class myTestClassMock {
  public function needsToBeCalled() {
    $foo = new Class_To_Mock();
    $result = $foo->needsToBeCalled();
    return array('option');
  }
}

Found this in the PHPUnit Manual under Chapter 11. Test Doubles

Limitations

Please note that final, private and static methods cannot be stubbed or mocked. They are ignored by PHPUnit's test double functionality and retain their original behavior.

like image 162
Mike B Avatar answered Oct 27 '22 01:10

Mike B


I just stumbled upon this issue today. Another alternative is to mock the interface that the class implements, given that it implements an interface and you use the interface as type hinting.

For example, given the problem in question, you can create an interface and use it as follows:

interface Interface_To_Mock
{
    function needsToBeCalled($options);
}

class Class_To_Mock implements Interface_To_Mock
{
    final public function needsToBeCalled($options)
    {
        ...
    }

}

class Class_To_Be_Tested
{
    public function setClassToMock(Interface_To_Mock $classToMock)
    {
        ...
    }

    ...
}

class MyTest extends PHPUnit_Framework_TestCase
{
    public function testDoSomething()
    {
        $mock = $this->getMock('Interface_To_Mock', array('needsToBeCalled'));
        ...
    }
}
like image 25
hidro Avatar answered Oct 27 '22 01:10

hidro