PHPUnit: Annotations vs Methods

 

 

 

Ever since I added the @expectedException annotation to PHPUnit I considered using it a best practice. This was reflected in PHPUnit's documentation as well as in my conference presentations and trainings, for instance.
A New Best Practice: expectException()

One morning not too long ago I got a phone call from Stefan Priebsch. He had just discussed the topic of testing exceptions with the students of his master class at the University of Rosenheim. One of his students had used the setExpectedException() method in his homework. When Stefan told him that he should have used the @expectedException annotation, the student challenged that best practice. After discussing the topic over the phone I had to admit that the student was right. While the @expectedException annotation is convenient to use it is also problematic. Lets look at the problems the student pointed out.

Back when the annotation was added to PHPUnit there was no support for namespaces in PHP. These days, though, namespaces are commonly used in PHP code. And since an annotation such as @expectedException is technically only a comment and not part of the code you have to use a fully-qualified class name such as vendor\project\Example when you use it. In a comment you cannot use an unqualified class name, Example for instance, that you would be able to use in code when that class is in or imported into the current namespace.

1 2 3 4 5 6 7 8 9 10 11 12
    
< ? php
namespace  vendor \ project ;

class  ExampleTest  extends  \ PHPUnit_Framework_TestCase
{
    public  function  testExpectedExceptionIsRaised ( )
    {
        $this -> expectException ( ExpectedException :: class ) ;

        // ...
    }
}

In the example above, we use the expectException() method that was introduced in PHPUnit 5.2 to tell PHPUnit that the test expects an exception of a specified type to be raised. Thanks to the class constant that holds the fully-qualified name of a class we do not need to write vendor\project\ExpectedException in the test code, but we can write ExpectedException::class instead. This improves the readability of the test and makes automated refactorings in modern IDEs such as PhpStorm reliable.

Another advantage of setting up the expectation in the test code has to do with the three phases of a test we discussed earlier. When we use the @expectedException annotation then PHPUnit will consider the test successful if the exception is raised at any point in time during the execution of the test method. This may not always be what you want:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
    
< ? php
namespace  vendor \ project ;

class  ExampleTest  extends  \ PHPUnit_Framework_TestCase
{
    public  function  testExpectedExceptionIsRaised ( )
    {
        // Arrange
        $example  =  new  Example ;

        // Assert
        $this -> expectException ( ExpectedException :: class ) ;

        // Act
        $example -> foo ( ) ;
    }
}

PHPUnit will consider the test shown in the example above then and only then a success when the expected exception is raised after the call to the expectException() method. If the constructor of the Example class raises an exception of the same type then this will be considered an error by PHPUnit.

In addition to the expectException() method, PHPUnit 5.2 also introduces the expectExceptionCode(), expectExceptionMessage, and expectExceptionMessageRegExp() methods for programmatically setting expectations for exceptions. There is nothing that you can do with these new methods that you could not have done with the old setExpectedException() method. However, because setExpectedException() can do everything these new methods can its API was convoluted and inconvenient. Separation of concerns, anyone? The setExpectedException() method has been deprecated and will be removed in PHPUnit 6.

Tags