How do I test for multiple exceptions with PHPUnit?
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->f开发者_StackOverflow社区ail('No exception thrown');
unset($hit);
try {
foo(1);
} catch (Exception $e) {
$hit = true;
}
if (!isset($hit))
$this->fail('No exception thrown');
unset($hit);
}
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);
}
}
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
}
Slightly cleaner code (but I'd still suggest splitting your tests:
try {
foo(-1);
$this->fail('No exception thrown');
} catch (Exception $e) {}
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) {
}
}
}
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.
精彩评论