🐶
PHP

PHPUnit Assert Exception Thrown: A Guide

By Filip on 11/07/2024

Learn how to use PHPUnit assertions to effectively test for expected exceptions in your PHP code and ensure robust exception handling.

PHPUnit Assert Exception Thrown: A Guide

Table of Contents

Introduction

Testing for exceptions is a crucial part of writing robust PHP code. PHPUnit, the de facto testing framework for PHP, provides several methods for asserting that exceptions are thrown when expected. This introduction will guide you through the basics of exception testing in PHPUnit, covering how to test for specific exception types, exception messages, and the absence of exceptions. Additionally, we'll touch upon exception testing in the context of Laravel's HTTP tests.

Step-by-Step Guide

To test for exceptions in PHPUnit, you can use the expectException() method. This method takes the name of the exception class you expect to be thrown.

public function testDivisionByZero() {
    $this->expectException(DivisionByZeroError::class);
    1 / 0;
}

You can also test for specific exception messages using expectExceptionMessage():

$this->expectExceptionMessage('Division by zero');

To assert that an exception is not thrown, you can use a try-catch block and then call fail():

try {
    // Code that should not throw an exception
    $this->assertTrue(true); 
} catch (Exception $e) {
    $this->fail('An exception was thrown when it should not have been.');
}

In Laravel, you can test for exceptions thrown in HTTP contexts using the seeStatusCode() method:

$this->get('/some-route')->seeStatusCode(404);

Remember that assertions in PHP 8 and above throw exceptions by default.

Code Example

This code provides examples of how to test for exceptions in PHPUnit tests. It shows how to test for specific exception types and messages using expectException and expectExceptionMessage. It also demonstrates how to assert that an exception is not thrown using a try-catch block and the fail method. Additionally, it includes an example of testing for exceptions in Laravel HTTP contexts using the assertStatus method to check for a specific HTTP status code.

This code example demonstrates different ways to test for exceptions in PHPUnit:

1. Testing for a specific exception type:

<?php

use PHPUnit\Framework\TestCase;

class ExceptionTest extends TestCase
{
    public function testDivisionByZeroThrowsException()
    {
        $this->expectException(DivisionByZeroError::class);
        1 / 0;
    }
}

2. Testing for a specific exception message:

<?php

use PHPUnit\Framework\TestCase;

class ExceptionTest extends TestCase
{
    public function testDivisionByZeroThrowsExceptionWithMessage()
    {
        $this->expectException(DivisionByZeroError::class);
        $this->expectExceptionMessage('Division by zero');
        1 / 0;
    }
}

3. Asserting that an exception is not thrown:

<?php

use PHPUnit\Framework\TestCase;

class ExceptionTest extends TestCase
{
    public function testNoExceptionThrown()
    {
        try {
            // Code that should not throw an exception
            $result = 2 + 2;
            $this->assertEquals(4, $result);
        } catch (Exception $e) {
            $this->fail('An exception was thrown when it should not have been: ' . $e->getMessage());
        }
    }
}

4. Testing for exceptions in Laravel HTTP contexts:

<?php

use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function testRouteNotFound()
    {
        $response = $this->get('/non-existent-route');

        $response->assertStatus(404);
    }
}

Note: This example uses Laravel's built-in testing features.

These examples demonstrate how to use expectException(), expectExceptionMessage(), fail(), and Laravel's seeStatusCode() (or assertStatus()) to test for exceptions in your PHPUnit tests. Remember that assertions in PHP 8 and above throw exceptions by default, so you can use try-catch blocks to handle them.

Additional Notes

  • Purpose of Exception Testing: Exception testing is not about testing the exception class itself (PHPUnit handles that). It's about ensuring your code throws the correct exception under specific circumstances. This helps guarantee your application handles errors predictably.

  • Alternatives to fail(): While try-catch with fail() works for asserting no exceptions, it can be less readable. PHPUnit offers:

    • expectNotToPerformAssertions(): Use this inside the try block if you expect the code to run without triggering any assertions (including exceptions).
    • doesNotThrowException() (deprecated): This method is less flexible and generally discouraged in favor of the above approaches.
  • Exception Hierarchy: expectException() checks for the specified exception or any of its subclasses. If you need to be highly specific, consider additional checks within the try block.

  • Beyond expectExceptionMessage():

    • expectExceptionCode(): Verify the exception code if your application uses custom codes.
    • Regular Expressions: For more complex message matching, use expectExceptionMessageMatches() with a regular expression.
  • Laravel Specifics:

    • assertStatus() vs. seeStatusCode(): While both check HTTP status codes, assertStatus() is generally preferred in newer Laravel versions for its clearer error messages.
    • Testing Exception Handling: Laravel allows you to test custom exception handlers. You can assert on the rendered view, redirected URL, or other aspects of your error handling logic.
  • Best Practices:

    • Test for Specific Exceptions: Avoid generic Exception catches in tests. Be as specific as possible to ensure the correct error is being handled.
    • One Exception per Test: Ideally, each test should focus on a single exception scenario for clarity and maintainability.
    • Meaningful Messages: Use descriptive exception messages in your code and assertions to make debugging easier.

Summary

Feature Description Example
Expecting an exception Use expectException(ExceptionClass::class) to assert that a specific exception is thrown. $this->expectException(DivisionByZeroError::class);
Expecting an exception message Use expectExceptionMessage('Error message') to assert that the exception message matches. $this->expectExceptionMessage('Division by zero');
Asserting no exception is thrown Use a try-catch block and call $this->fail() if an exception is caught. php try { $this->assertTrue(true); } catch (Exception $e) { $this->fail('Unexpected exception.'); }
Testing exceptions in Laravel HTTP contexts Use seeStatusCode(statusCode) to assert the HTTP status code returned. $this->get('/some-route')->seeStatusCode(404);
PHP 8+ behavior Assertions in PHP 8 and above throw exceptions by default.

Conclusion

Exception testing is a cornerstone of robust PHP development, and PHPUnit provides the tools to make it straightforward. By incorporating the techniques outlined in this article—testing for specific exception types, messages, and even the absence of exceptions—you can ensure your PHP applications are robust, reliable, and predictable in the face of errors. Remember to leverage PHPUnit's assertions and Laravel's HTTP testing features to streamline your exception testing workflow. By embracing a test-driven approach and paying close attention to exception handling, you can create high-quality PHP applications that stand up to real-world usage.

References

Were You Able to Follow the Instructions?

😍Love it!
😊Yes
😐Meh-gical
😞No
🤮Clickbait