An Overview of PHPUnit 5 – What’s New? What’s Coming?

Share this article

This post has been peer reviewed by Cláudio Ribeiro and Thomas Punt. Many thanks to the reviewers who keep SitePoint’s content perpetually excellent!


It was January 2016 when the PHPUnit development team announced the release of PHPUnit 5.0.

Color pen on paper, putting checkmarks into some boxes and red X-es into others

While several minor version have already been released since, PHPUnit’s major version 5 has introduced several new functionalities and deprecated a few others. In this article, we’ll take a look at the most notable changes.

Requirements

PHPUnit 5 requires PHP 5.6. They have dropped support for PHP 5.3, 5.4, and 5.5 as they are not actively supported by the PHP project, according to their release process. You can see the full list of required PHP extensions here.

New Assertion Methods

Three assertion methods including assertFinite(), assertInfinite() and assertNan() have been added to the stack.

assertFinite() asserts that the expected value is a finite value, meaning it is a legal number within the allowed range for a PHP float on the platform on which the test runs. Behind the scenes, it uses PHP’s built-in function is_finite, to check if the constraint is met.

Consider the following test:

<?php
class FiniteTest extends PHPUnit_Framework_Testcase
{
    public function testFinite()
    {
        $this->assertFinite(INF);
    }
}

The above test would fail since INF is a PHP constant, representing an infinite value.

assertInfinite() verifies whether the actual value is an infinite number (either positive or negative), meaning a value too big to fit into a float on the current platform. This method uses PHP’s is_infinite function.

<?php
class InfiniteTest extends PHPUnit_Framework_Testcase
{
    public function testInfinite()
    {
        $this->assertInfinite(INF);
    }
}

The above assertion would give us the green light since INF is an infinite value.

The third assertion is assertNan(), which fails if the expected value is not a NAN value.
As you probably know, NAN means: not a number, which is the result of an invalid calculation in PHP, like acos(8).

<?php
class NanTest extends PHPUnit_Framework_Testcase
{
    public function testNan()
    {
        $this->assertNan(14);
    }
}

The above test also fails, because the actual value is 14 while the expected value is a NAN.

All the above methods take the same arguments: the first argument is the actual value, and the second one (which is optional) is the message to print if the assertion fails.

Deep Cloning of Passed-in Objects

In PHPUnit versions prior to 5.0, a passed object (by using @depends) is passed as-is, meaning it is a reference to the actual object (not a clone). Consider the following code:

<?php

class Foo
{
    public $bars = [];
}

class Bar
{
    public $name;

    function __construct($name)
    {
        $this->name = $name;
    }
}

class FooTest extends PHPUnit_Framework_TestCase
{
    public function testProducer()
    {
        $foo = new Foo();
        $foo->bars[] = new Bar('James');
        $foo->bars[] = new Bar('Kirk');
        $foo->bars[] = new Bar('Rob');

        $this->assertEquals(3, count($foo->bars));
        $this->assertEquals('Rob', $foo->bars[2]->name);

        return $foo;
    }

    /**
     * @depends testProducer
     */
    public function testFirstDependent($foo)
    {
        $foo->bars[0]->name = 'Lars';
        $this->assertEquals('Kirk', $foo->bars[1]->name);
    }

    /**
     * @depends testProducer
     */
    public function testSecondDependent($foo)
    {
        $this->assertEquals('James', $foo->bars[0]->name);
    }
}

Here’s the story: we have a class named Foo, which stores instances of class Bar.
In our test class, we have three test methods: testProducer(), testFirstDependent(), and testSecondDependent(). The last two methods depend on the return value of testProducer().

When using @depends, $foo is passed by reference, meaning both dependent tests receive the same instance of the object. This means any modifications on the object within one of the test methods will affect the object within the other test method.

That said, testFirstDependent() changes $foo->bars[0]->name to James. This causes testSecondDependent() to fail, since $foo->bars[0] has been already modified by testFirstDependent().

This is the test result in PHPUnit version 4.8.24:

PHPUnit 4.8.24 by Sebastian Bergmann and contributors.

..F

Time: 75 ms, Memory: 5.00Mb

There was 1 failure:

1) FooTest::testSecondDependent
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'James'
+'Lars'

/var/www/phpunit-example/tests/FooTest.php:47

FAILURES!
Tests: 3, Assertions: 4, Failures: 1.

Since PHPUnit 5.0, passed objects can be deeply cloned by using @depends clone. If we run the above the test (with @depends clone annotation in version 5.0 onwards), we’ll get a green light since each test is modifying and asserting its own copy of the passed object.

<?php

// ...

class FooTest extends PHPUnit_Framework_TestCase
{
    public function testProducer()
    {
        $foo = new Foo();
        $foo->bars[] = new Bar('James');
        $foo->bars[] = new Bar('Kirk');
        $foo->bars[] = new Bar('Rob');

        $this->assertEquals(3, count($foo->bars));
        $this->assertEquals('Rob', $foo->bars[2]->name);

        return $foo;
    }

    /**
     * @depends clone testProducer
     */
    public function testFirstDependent($foo)
    {
        $foo->bars[0]->name = 'Lars';
        $this->assertEquals('Kirk', $foo->bars[1]->name);
    }

    /**
     * @depends clone testProducer
     */
    public function testSecondDependent($foo)
    {
        $this->assertEquals('James', $foo->bars[0]->name);
    }
}

Here’s the test result in PHPUnit version 5.3.2:

PHPUnit 5.3.2 by Sebastian Bergmann and contributors.

...                                                                 3 / 3 (100%)

Time: 86 ms, Memory: 3.50Mb

OK (3 tests, 4 assertions)

Passing Mock Objects with Expectations

Since PHPUnit 5.0, we can pass mock objects along with their expectations (an expectation asserts when and how a certain method is called) to the dependent test methods. This is not thoroughly possible in PHPUnit’s versions prior to version 5.0. Also, we can pass mock objects to the dependent test methods, but without any expectations. Consider the following example:

<?php
class fooTest extends PHPUnit_Framework_TestCase
{
    public function testProducer()
    {
        $fooMock = $this->getMock('Foo');

        $fooMock->expects($this->once())
                ->methods('bar')
                ->will($this->returnValue(true));

        return $fooMock;
    }

    /**
     * @depends clone testProducer
     */
    public function testDependant($mock)
    {
        // This will fail in versions prior to 5.0.0 as the actual value will be NULL
        $this->assertTrue(true, $mock->bar())
    }
}

The above test would fail in PHPUnit versions prior to 5.0, because we’re expecting true while the actual value is null. The reason is that the testProducer method returns the mock object without the expectations, meaning all methods will return null.

PHPUnit 4.8.24 by Sebastian Bergmann and contributors.

.F

Time: 74 ms, Memory: 5.00Mb

There was 1 failure:

1) MockTest::testDependent
Failed asserting that null is true.

/var/www/phpunit-example/tests/MockTest.php:31

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

PHPUnit 5 will give us the green light since all mocks are passed along with their expectations.

PHPUnit 5.3.2 by Sebastian Bergmann and contributors.

..                                                                  2 / 2 (100%)

Time: 57 ms, Memory: 3.75Mb

OK (2 tests, 2 assertions)

Whitelisting File for Code Coverage Is Now Mandatory

Since PHPUnit 5.0, it is mandatory to configure a whitelist for code coverage analysis. In versions prior to 5.0, we only get a warning that no whitelist has been configured for code coverage, but the report would be generated anyway:

PHPUnit 4.8.24 by Sebastian Bergmann and contributors.
Warning:    No whitelist configured for code coverage

...........

Time: 2.42 seconds, Memory: 15.50Mb

OK (11 tests, 27 assertions)

Generating code coverage reports in HTML format ... done

Since version 5.0, we get an error and no code coverage would be generated:

PHPUnit 5.3.2 by Sebastian Bergmann and contributors.

Error:         No whitelist configured, no code coverage will be generated 

We should always configure the whitelist in phpunit.xml:

<filter>
  <whitelist processUncoveredFilesFromWhitelist="true">
    <directory suffix=".php">/path/to/files</directory>
    <file>/path/to/file</file>
    <exclude>
      <directory suffix=".php">/path/to/files</directory>
      <file>/path/to/file</file>
    </exclude>
  </whitelist>
</filter>

Convenient Way of Creating Constructor-Less Mocks

In PHPUnit 5 we can directly create a mock with a disabled constructor by using getMockWithoutInvokingTheOriginalConstructor() method. This method is actually a wrapper for the disableOriginalConstructor() method:

<?php
public function getMockWithoutInvokingTheOriginalConstructor($originalClassName)
    {
        return $this->getMockBuilder($originalClassName)
                    ->disableOriginalConstructor()
                    ->getMock();
    }

According to the above source code we can use it like this:

<?php
class MockTest extends PHPUnit_Framework_TestCase
{
    // ...
    protected $mock;
    public function setUp()
    {
        $this->mock = $this->getMockWithoutInvokingTheOriginalConstructor('Foo');
        $this->mock->expects($this->any())
             ->method('bar')
             ->will($this->returnValue(true));
    }
    // ...
}

New Options in the Command-Line

PHPUnit 5 has introduced several options in the command line. One of these options is --reverse-list, which allows test failures to be sorted in reverse, so the first failure is printed at the bottom. This option is really handy when several tests depend on one test to pass, and if we fix the first one, the rest might be resolved automatically.

The other new option is --whitelist, to configure a whitelist for code coverage analysis:

phpunit --whitelist /path/to/php/files

There’s another option, which might be handy in some cases: --atleast-version.
This option takes a number as the minimum PHPUnit version, and checks if the current version is greater than this value. We can use it to run the tests only if a certain version of PHPUnit is installed:

phpunit --atleast-version 5.3 && phpunit --whitelist path/to/php/files

Removed Functionality

Functionality that was marked deprecated in PHPUnit 4 has been removed in version 5.

The following assertions have been removed:

  • assertSelectCount()
  • assertSelectRegExp()
  • assertSelectEquals()
  • assertTag()
  • assertNotTag()

The --strict command-line option and strict attribute in the configuration file no longer exist.

PHPUnit_Selenium is no longer bundled in the PHAR distributions and all the related settings in the configuration file have been removed.

To see the full list of the added, changed and removed functionality and bug fixes, you may visit this page.

Wrapping Up

PHPUnit has dramatically changed some features, and thereby confused some developers who weren’t ready for it – we hope our post helps clear things up. A few assertions have been added to the stack and a few others have been deprecated. PHPUnit 5 has dropped support for PHP versions prior to 5.6. PHPUnit 6 has been scheduled for February 3, 2017 and will no longer support PHP 5.

Have you upgraded to PHPUnit 5 yet? If you have any comments or suggestions, or if there’s anything we missed in this article, please let us know in the comments!

Reza LavarianReza Lavarian
View Author

A web developer with a solid background in front-end and back-end development, which is what he's been doing for over ten years. He follows two major principles in his everyday work: beauty and simplicity. He believes everyone should learn something new every day.

BrunoSOOPHPPHPphpunittddunit testunit testingUnit Tests
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week