Reputation: 2783
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);
}
Upvotes: 31
Views: 14509
Reputation: 305
You could create a method like this in your test case class
public function assertThrowsException(\Closure $closure, string $exception)
{
try {
$closure();
$this->fail("Closure doesn't throw any exception");
} catch (PHPUnit\Framework\AssertionFailedError $e) {
throw $e;
} catch (\Exception $e) {
$this->assertInstanceOf($exception, $e, "Wrong exception type");
}
}
Then call it like
$this->assertThrowsException(function() {
foo(1);
}, SomeException::class);
$this->assertThrowsException(function() {
foo(-1);
}, AnotherException::class);
Some improvements might be needed here, there might be some edge cases, but I think it could take you a long way.
Upvotes: 3
Reputation: 5220
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);
}
}
Upvotes: 38
Reputation: 19099
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) {
}
}
}
Upvotes: 3
Reputation: 15415
Slightly cleaner code (but I'd still suggest splitting your tests:
try {
foo(-1);
$this->fail('No exception thrown');
} catch (Exception $e) {}
Upvotes: 8
Reputation: 35139
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
}
Upvotes: 21