expected exceptions annotations, mocked object calls, oh my.

Note: I have tested this in PHPUnit 3.4.1 and haven’t tried it out in 3.5.
Anyone who has worked with PHPUnit has most likely worked with expected exceptions and mock objects. The nice thing about working with expected exceptions is that we have access to a handy @expectedException annotation. I’ve gotten into the habit of using this for exceptions my fixtures should throw but also for when I’m using a mock object to verify a method call. So my tests usually expect foo_exception for fixture throws and when i’m testing method calls via a mock, they expect Exception. Therein lies my problem. Because all my custom class exceptions obviously extend the Exception class, I can get some false positives in testing.

require_once 'Zend/Loader/Autoloader.php';
$loader = Zend_Loader_Autoloader::getInstance();
require_once('foo.php');
class tmpTest extends PHPUnit_Framework_Testcase
{

    /**
     * @expectedException Exception
     */
    public function testFooBar()
    {
        $foo=new foo();
        $foo->bar();
    }

    /**
     * @expectedException Exception
     */
    public function testBarBaz()
    {
        $mock=$this->getMock('foo',array('baz'));
        $mock->expects($this->any())
         ->method('baz')
         ->will($this->throwException(new Exception('baz')));
        $mock->barbaz();
    }
}
class foo_exception extends Exception{}

class foo
{
    public function bar()
    {
        throw new foo_exception('bar');
    }

    public function baz()
    {
        echo "bwahn";
    }

    public function barbaz()
    {
        $this->bar();
        $this->baz();
    }
}

So here we have an expectation for Exception but if we look at the code, we see that the bar method throws a foo_exception and the testBarBaz test is trying to test for the baz call via a mock that throws an Exception. if we change the annotation to expect foo_exception, the test still passes. This leads me to believe the best way to isolate the behavior we wish to test is to not use annotation for these sorts of tests. Or if you want to use annotation, be sure to use a unique exception for the mock. This means, unfortunately for me, that I’ll have to go back through all my tests and ensure there’s no false positives.

Lesson learned: be careful using shortcuts (and don’t stand in the fire).

On a side note, this part of PHPUnit is why those tests will behave that way. The behavior is completely my fault but I wanted to confirm it was behaving because of how it was verifying the expected exception.

Enhanced by Zemanta