An Overview of PHPUnit 5 – What’s New? What’s Coming?
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.
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!