Testing Exceptions and Error Handling in PHPUnit

Testing Exceptions and Error Handling in PHPUnit

Learn how to test exceptions and error handling in PHPUnit. Verify that your PHP code correctly throws and handles exceptions using PHPUnit’s built-in methods.

Introduction

Proper exception handling is essential in PHP applications to ensure reliable error management and predictable system behavior. When writing unit tests, it's important to verify that exceptions are thrown correctly in various failure scenarios.

PHPUnit provides methods to:

  • Verify that a function throws the correct exception
  • Ensure proper handling of exceptions in try-catch blocks
  • Check custom exception messages and codes
  • Test warnings, errors, and fatal conditions

This guide covers:

  • How to test exceptions using expectException()
  • Testing specific exception messages and codes
  • Handling warnings and deprecated functions in tests
  • Best practices for writing maintainable exception tests

1. Understanding Exception Testing in PHPUnit

When a function throws an exception, PHPUnit allows us to assert that the correct exception type is thrown.

Example: A Function That Throws an Exception

class UserService
{
    public function findUserById($id)
    {
        if ($id <= 0) {
            throw new InvalidArgumentException("User ID must be greater than zero.");
        }
        return "User Found";
    }
}

The function throws an exception if the ID is zero or negative. Let's test this behavior.

2. Using expectException() to Test Exception Throwing

Testing if an Exception is Thrown

use PHPUnit\Framework\TestCase;

class UserServiceTest extends TestCase
{
    public function testFindUserByIdThrowsException()
    {
        $userService = new UserService();

        $this->expectException(InvalidArgumentException::class);

        $userService->findUserById(0); // This should trigger the exception
    }
}

How This Works:

  • expectException(InvalidArgumentException::class) tells PHPUnit to expect an exception.
  • If the function does not throw the exception, the test will fail.

3. Testing Exception Messages and Codes

Sometimes, you need to check specific exception messages or codes.

Example: Testing Exception Message

public function testFindUserByIdThrowsCorrectMessage()
{
    $userService = new UserService();

    $this->expectException(InvalidArgumentException::class);
    $this->expectExceptionMessage("User ID must be greater than zero.");

    $userService->findUserById(-5);
}

Example: Testing Exception Code

If your exception has a specific error code, you can test for it.

class UserService
{
    public function findUserById($id)
    {
        if ($id <= 0) {
            throw new InvalidArgumentException("Invalid ID", 1001);
        }
        return "User Found";
    }
}

public function testFindUserByIdThrowsCorrectCode()
{
    $userService = new UserService();

    $this->expectException(InvalidArgumentException::class);
    $this->expectExceptionCode(1001);

    $userService->findUserById(-10);
}

These assertions help ensure that specific error cases trigger the correct response.

4. Handling Exceptions in Try-Catch Blocks

In some cases, exceptions are caught inside the function being tested. PHPUnit can still verify correct handling.

Example: Testing Exception Handling in a Try-Catch Block

class PaymentService
{
    public function processPayment($amount)
    {
        try {
            if ($amount < 0) {
                throw new RuntimeException("Negative amount not allowed.");
            }
            return "Payment processed";
        } catch (RuntimeException $e) {
            return "Error: " . $e->getMessage();
        }
    }
}

Unit Test for Proper Exception Handling

public function testProcessPaymentHandlesException()
{
    $paymentService = new PaymentService();

    $result = $paymentService->processPayment(-50);

    $this->assertEquals("Error: Negative amount not allowed.", $result);
}

This ensures that:

  • The exception is properly caught.
  • The function returns a meaningful error message instead of crashing.

5. Testing PHP Warnings and Deprecated Features

PHPUnit can also handle warnings and deprecation notices.

Testing for PHP Warnings

If a function triggers a warning, use expectWarning().

public function testDivisionByZeroTriggersWarning()
{
    $this->expectWarning();

    $result = 10 / 0; // This triggers a PHP warning
}

Testing Deprecated Function Usage

If a function is marked as deprecated, use expectDeprecation().

public function testDeprecatedFunction()
{
    $this->expectDeprecation();

    trigger_error("This function is deprecated", E_USER_DEPRECATED);
}

This is useful when testing legacy code that is being phased out.

6. Testing Custom Exception Classes

Custom exceptions allow better error classification in large applications.

Example: Defining a Custom Exception

class UserNotFoundException extends Exception {}

Throwing the Custom Exception

class UserService
{
    public function getUser($id)
    {
        if ($id > 100) {
            throw new UserNotFoundException("User not found", 404);
        }
        return "User Found";
    }
}

Testing Custom Exception Handling

public function testUserNotFoundExceptionIsThrown()
{
    $userService = new UserService();

    $this->expectException(UserNotFoundException::class);
    $this->expectExceptionMessage("User not found");
    $this->expectExceptionCode(404);

    $userService->getUser(150);
}

This ensures that:

  • The correct exception class is thrown.
  • The exception message is meaningful.
  • The error code correctly represents the issue.

7. Best Practices for Testing Exceptions in PHPUnit

  • Always test for the correct exception class using expectException().
  • Verify exception messages and codes when error details matter.
  • Use try-catch assertions for functions that handle exceptions internally.
  • Ensure warnings and deprecated features are handled using expectWarning() and expectDeprecation().
  • Favor meaningful custom exception classes over generic ones.

Conclusion

Exception handling is a critical part of robust PHP applications, and PHPUnit provides powerful tools for testing exceptions correctly.

This guide covered:

  • How to use expectException() for exception testing
  • Testing exception messages and error codes
  • Handling warnings, errors, and deprecated functions
  • Verifying exception handling inside try-catch blocks
  • Writing maintainable exception tests using best practices

By implementing these strategies, you can ensure that your PHP code handles errors gracefully and consistently, reducing the risk of unexpected failures in production.

Leave a Reply