Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test exceptions using a data provider in PHPUnit?

PHPUnit has a very useful feature @dataProvider, that allows to test multiple cases of a method. It also has another cool annotation -- @expectedException to ensure the application throws a correct Exception at a defined place.

I'm currently testing a method against multiple edge cases and would like to combine these two features like this (not working code):

class TestMyClass
{
    /**
     * @dataProvider provideDataForFoo
     */
    public function testFoo($paramBar, $paramBuz, $expected)
    {
        $myObject = new MyClass();
        $this->assertEquals($expected, $myObject->foo($paramBar, $paramBuz));
    }
    public function provideDataForFoo()
    {
        return [
            ['expected lorem', 'bar lorem', 'buz lorem'],
            ['expected ipsum', 'bar ipsum', 'buz ipsum'],
            ['expected exception', 'bar invalid argument', 'buz invalid argument'],
        ];
    }
}

Is possible / How to use @expectedException as one of the cases, when using @dataProvider?

like image 542
automatix Avatar asked Sep 14 '16 09:09

automatix


2 Answers

PHPUnit doesn't provide this combination. But this can be implemented with a simple trick:

  1. basic solution

Separate test methods for normal and exception testing.

class TestMyClass
{
    /**
     * @dataProvider provideDataForFoo
     */
    public function testFoo($paramBar, $paramBuz, $expected)
    {
        $myObject = new MyClass();
        $this->assertEquals($expected, $myObject->foo($paramBar, $paramBuz));
    }
    public function provideDataForFoo()
    {
        return [
            ['expected lorem', 'bar lorem', 'buz lorem'],
            ['expected ipsum', 'bar ipsum', 'buz ipsum'],
        ];
    }
    /**
     * @dataProvider provideDataForFooException
     */
    public function testFooException($paramBar, $paramBuz, $expectedException)
    {
        $myObject = new MyClass();
        $this->expectException($expectedException);
        $myObject->foo($paramBar, $paramBuz);
    }
    public function provideDataForFooException()
    {
        return [
            ['expected exception', 'bar invalid argument', '\My\Exception\Fully\Qualified\Name'],
        ];
    }
}
  1. extended solution

a. One test method and using the Reflection API.

We have only one test method. The data provider method returns an array, where to elements for the $expected test method input can be Exceptions. If the $expected is an Exception we handle this case with expectException(...), otherwise as a "normal" test case.

b. One test method and using an "exception" flag.

Theoretically a method can return an Exception. To consider this case we have to introduce a flag like "testItForException" and provide this information to the test method. It also can be a further element, e.g. exception, in the array returned by the data provider method (and then in the test method: if(! (empty($exception)) { test it as normal } else {expect exception})).

like image 108
automatix Avatar answered Sep 18 '22 10:09

automatix


Instead of the annotation you can also use $this->setExpectedExceptionRegExp() with the arguments

$exceptionName — mixed (class name or exception instance)
$exceptionMessageRegExp — string (optional regular expression)
$exceptionCode — integer (optional exception code)

Note: The old setExpectedException() method has been deprecated in PHPUnit 5.2

This means, you can pass an exception class name via data provider. If it is not empty, call setExpectedExceptionRegExp()

Another advantage of the method over the annotation is that you can be more specific about where the exception is expected, if you don't call the method at the beginning of the test.

like image 25
Fabian Schmengler Avatar answered Sep 17 '22 10:09

Fabian Schmengler