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()
andexpectDeprecation()
. - 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.