tags:

views:

404

answers:

2

I´m trying to test if a protected method is called in a public interface.

<?php
abstract class SomeClassAbstract
{ 
    abstract public foo();

    public function doStuff() 
    {    
        $this->_protectedMethod();
    }

    protected function _protectedMethod();
    {
        // implementation is irrelevant
    }
}

<?php
class MyTest extends PHPUnit_Framework_TestCase
{
    public function testCalled()
    {
        $mock = $this->getMockForAbstractClass('SomeClass');
        $mock->expects($this->once())
             ->method('_protectedMethod');

        $mock->doStuff();
    }
}

I know it is called correctly, but PHPUnit says its never called.

The same happens when I test the other way, when a method is never called:

<?php
abstract class AnotherClassAbstract
{ 
    abstract public foo();

    public function doAnotherStuff() 
    {    
        $this->_loadCache();
    }

    protected function _loadCache();
    {
        // implementation is irrelevant
    }
}

<?php
class MyTest extends PHPUnit_Framework_TestCase
{
    public function testCalled()
    {
        $mock = $this->getMockForAbstractClass('AnotherClass');
        $mock->expects($this->once())
             ->method('_loadCache');

        $mock->doAnotherStuff();
    }
}

The method is called but PHPUnit says that it is not.

What I´m doing wrong?

Edit I wasn´t declaring my methods with double colons, it was just for denoting that it was a public method (interface). Updated to full class/methods declarations.

Edit 2 I should have said that I´m testing some method implementations in an abstract class (edited the code to reflect this). Since I can not instantiate the class, how can I test this?

I´m thinking in creating an SomeClassSimple extending SomeClassAbstract and testing this one instead. Is it the right approach?

+2  A: 

In the version of PHPUnit that I have, the class PHPUnit_Framework_MockObject_Mock uses PHP's get_class_methods to determine the interface of the object being mocked. get_class_methods will pick out only the public methods, not the protected or private ones.

This is in keeping with the spirit of xUnit unit testing. Consider the PHPUnit docs' example on how to use Mock Objects. There, the SUT is Subject, which has a protected method notify. The method being tested, though, is doSomething. Considering Subject as a black box, we don't care about the details of how it is implemented. We do care, though, about its behavior. Specifically, we require that when we call doSomething, then the update method of any attached observers is called. So we mock the third party object Observer, and we set up the expectation that its update method will be called. Note that although the protected method notify is exercised, it is not named explicitly in the test. That gives us the freedom to change the implementation any time we like, just so long as the behavior is preserved.

Returning to your example.

class MyTest extends PHPUnit_Framework_TestCase
{
  public function testCalled()
  {
    $mock = $this->getMock('SomeClass');
    $mock->expects($this->once())
         ->method('_protectedMethod');

    $mock->doStuff();
  }
}

Here, your testCalled method creates no instance of the System Under Test (SUT), which would be SomeClass. The version of doStuff in the mock of SomeClass doesn't do anything. In particular, it doesn't call _protectedMethod.

Ewan Todd
Thank you Ewan, you´re right. Of course if its a mock object it doesn´t have the implementation of the method. How dumb I an. I will mark your answer as correct. If you can, please, see my second edit.
Luiz Damim
So what I´m trying to test is the implementation, not the behavior, right? It´s a little more clear to me, thank you.
Luiz Damim
Unit testing is, I think, more of an art than it seems. I appreciated it more after reading xUnit Test Patterns: Refactoring Test Code by Gerard Meszaros. In turn, the book was a strong recommendation of Sebastian Bergmann, the PHPUnit author.
Ewan Todd
+1  A: 

It looks like you simply need to tell PHPUnit that you want to mock out that function, i.e.:

$mock = $this->getMock('SomeClass',
                        array('_protectedMethod'));
$mock->expects($this->once())
     ->method('_protectedMethod');

$mock->doStuff();
Kevin