28
votes

When testing for exceptions with PHPUnit, what is the best way to require that every statement or assertion must throw an exception in order for the test to pass?

I basically want to do something like this:

public function testExceptions()
{

    $this->setExpectedException('Exception');

    foo(-1); //throws exception
    foo(1); //does not throw exception

}

//Test will fail because foo(1) did not throw an exception

I've come up with the following, which does the job, but is quite ugly IMO.

public function testExceptions()
{

    try {
        foo(-1);
    } catch (Exception $e) {
        $hit = true;
    }

    if (!isset($hit))
        $this->fail('No exception thrown');

    unset($hit);

    try {
        foo(1);
    } catch (Exception $e) {
        $hit = true;
    }

    if (!isset($hit))
        $this->fail('No exception thrown');

    unset($hit);

}
5

5 Answers

21
votes

As exceptions are such big events in the program flow, testing multiple ones in a single test is problematic.

The easiest thing is is to simply split it into two tests - the first requires an exception to be able to pass, the second simply runs, and would fail it it did throw one. You could add some other tests in the second if you wanted (confirming a return value maybe), but I'd be inclined to make sure it still did just the one essential thing, according to the naming of it.

/**
 * @expectedException Exception
 */
public function testBadFooThrowsException()
{
    // optional, can also do it from the '@expectedException x'
    //$this->setExpectedException('Exception');
    foo(-1); //throws exception -- good.
}

public function testFooDoesNotThrowException()
{
    foo(1); //does not throw exception
}
30
votes

I think this is a very common situation in unit testing. The approach I use in this cases is using phpunit dataProviders. All works as expected, and test code become more clear and concise.

class MyTest extends PHPUnit\Framework\TestCase
{
    public function badValues(): array
    {
       return [
           [-1],
           [1]
       ];
    }


    /**
     * @dataProvider badValues
     * @expectedException Exception
     */
    public function testFoo($badValue): void
    {
        foo($badValue);
    }
}
8
votes

Slightly cleaner code (but I'd still suggest splitting your tests:

try {
    foo(-1);
    $this->fail('No exception thrown');
} catch (Exception $e) {}
1
votes

This doesn't make sense to me.

I guess you are trying to test multiple separate things with one test case which is bad practice.

When foo() throws the expected exception the test case is successful and bar() won't run.

Just create two separate test cases which is a lot less code then what you produced in the second listing.

Or explain why it would make sense to run bar(), after foo() failed with an exception, when it will throw an exception too.

1
votes

Expanding on @dave1010's answer, here is how I solved this issue. This allows you to keep all these "assertions" neat and tidy within one test. You simply define an array of variables that should fail the test, and then loop through each one and see if an exception is raised. If any fail (no exception thrown), the test fails, otherwise the test passes.

<?php

public function testSetInvalidVariableType()
{
    $invalid_vars = array(
        '',                 // Strings
        array(),            // Arrays
        true,               // Booleans
        1,                  // Integers
        new \StdClass       // Objects
    );

    foreach ($invalid_vars as $var) {
        try {
            $object->method($var);
            $this->fail('No exception thrown for variable type "' . gettype($var) . '".');
        } catch (\Exception $expected) {
        }
    }
}