Let's have a shamefully simple class like this:
<?PHP
namespace App;
class Example
{
public function handle()
{
throw new \Exception('Not Found', 404);
}
}
Back in the days, there were three options for how to test the exception. From PHPunit 9.0 onwards, only two remain. expectedException annotation was deprecated and removed.
Now we can concentrate on existing solutions.
First one is a try-catch pair. I've seen Adam Wathan used it and advocate for it in his TDD course. Some devs consider this to be a big old fashioned. However, it has one advantage. Inside catch block, you can run additional assertion related to exception or even some other parts of the code, e.g. user wasn't updated.
/** @test*/
function exception_is_thrown()
{
try {
$example = new Example();
$example->handle();
} catch (\Exception $e) {
$this->assertSame('Not Found', $e->getMessage());
$this->assertSame(404, $e->getCode());
// additional assertion e.g. user wasn't updated
return;
}
$this->fail('Exception was not thrown.');
}
This approach is the PHPunit's recommended one. You can use four different methods expectException
, expectExceptionMessage
, expectExceptionMessageMatches
.
Be aware that apart from those 4, you can't use any other assertions. If you would add $this->assertTrue(false);
it would be ignored at the end of the test.
/** @test */
function exception_is_thrown()
{
$this->expectException(\Exception::class);
$this->expectExceptionCode(404);
$this->expectExceptionMessage('Not Found');
$this->expectExceptionMessageMatches('/Found/');
$example = new Example();
$example->handle();
}
They are both excellent strategies to test PHP exceptions. Personally, I use the second one by default. I would use the first one only if additional assertions are needed.