testing php applications

Testing Exceptions And Errors

Failure behaviour is part of an API. Tests should prove that invalid input fails in the intended way and that unexpected exceptions are not silently swallowed.

Expect The Right Failure

In PHPUnit, declare the expected exception before calling the code that should throw. Check the exception class and, when stable and meaningful, its message or code.

Do Not Catch Too Much

Production code should catch an exception only when it can add context, translate it at a boundary, retry safely, or recover. Tests should make accidental swallowing visible.

PHP example
<?php

declare(strict_types=1);

use PHPUnit\Framework\TestCase;

function positiveQuantity(int $quantity): int
{
    if ($quantity < 1) {
        throw new InvalidArgumentException('Quantity must be positive.');
    }

    return $quantity;
}

final class PositiveQuantityTest extends TestCase
{
    public function testZeroIsRejected(): void
    {
        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage('Quantity must be positive.');

        positiveQuantity(0);
    }
}

Set the expectation immediately before the call that should throw. This avoids accidentally satisfying the test with an earlier exception from setup code.

Common Mistakes

  • Testing that any exception occurs when the class matters.
  • Placing the expected-exception declaration after the throwing call.
  • Asserting fragile full messages that are not part of the contract.
  • Catching exceptions in production code only to hide them.

What To Practise

  • Test expected exception classes.
  • Distinguish domain rejection from unexpected failure.
  • Keep failure contracts deliberate.

Practice

Practice: Test Quantity Rejection

Write checks for a quantity validator that accepts positive integers and rejects zero or negatives.

Requirements

  • Show the successful case.
  • Catch and identify the expected exception class.
  • Check the stable message.
  • Include zero and a negative value.
Show solution

The loop verifies both rejected inputs.

PHP example
<?php

declare(strict_types=1);

function positiveQuantity(int $quantity): int
{
    if ($quantity < 1) {
        throw new InvalidArgumentException('Quantity must be positive.');
    }

    return $quantity;
}

echo positiveQuantity(2) . PHP_EOL;

foreach ([0, -4] as $quantity) {
    try {
        positiveQuantity($quantity);
    } catch (InvalidArgumentException $exception) {
        echo $exception->getMessage() . PHP_EOL;
    }
}

// Prints:
// 2
// Quantity must be positive.
// Quantity must be positive.

A PHPUnit test would use expectException() before calling the invalid case.