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.

Ordering matters. 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.

Note: This is real, correct PHPUnit code meant to be copied into your own project — this website's codebase has no PHPUnit installed, so nothing here runs live on this page.

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.

Next steps: Head to Data Providers to learn how to run the same test method against many different sets of input.