Testing Exceptions
So far every test has checked what code returns. Just as important is checking what code does when it's given bad input: does it throw the exception it's supposed to? PHPUnit has dedicated expectations for exactly this.
expectException()
Call $this->expectException(SomeException::class) to tell PHPUnit "the very next thing
that happens in this test should throw this exception." Then call the code that is supposed to throw it.
expectException() must be called before the code that
should throw — never after. PHPUnit doesn't scan the whole method for a thrown exception; it starts
watching from the moment you call expectException() and expects the exception to occur
somewhere later in that same test method, before the method ends.
expectExceptionMessage()
Checking the exception's class is often not enough — you may also want to confirm it carries the right
message. $this->expectExceptionMessage('...') does this; call it alongside
expectException(), before the code under test runs.
Worked Example: Division by Zero
Suppose Calculator::divide() is written to reject division by zero with a clear, custom
exception rather than letting PHP's own error surface:
<?php
class Calculator
{
public function divide($a, $b)
{
if ($b === 0) {
throw new InvalidArgumentException('Cannot divide by zero');
}
return $a / $b;
}
}
The test asserts both the exception class and its message:
<?php
use PHPUnit\Framework\TestCase;
class CalculatorTest extends TestCase
{
public function testDivideByZeroThrowsException()
{
$calculator = new Calculator();
// These two calls MUST come before the code that should throw
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Cannot divide by zero');
// The code under test - PHPUnit is now watching for the exception
$calculator->divide(10, 0);
}
}
A more low-level alternative is letting PHP's own DivisionByZeroError surface (which happens
automatically for intdiv() or the % operator in modern PHP) and asserting against
that built-in error class instead of a custom exception — the test would simply target
DivisionByZeroError::class rather than InvalidArgumentException::class.
When the Exception Never Happens
If a test calls expectException() but the code that follows completes normally without
throwing anything, the test itself fails — the expectation you set up was never satisfied. This is
exactly the behavior you want: it means a bug that silently stops throwing an exception it should still
throw will be caught by the test suite, just like any other regression.