Writing Tests for PHPUnit Using Data Providers
Introduction
In PHPUnit tests, methods can accept one or more arbitrary arguments. These arguments
can be provided by one or more methods known as data provider methods. The data provider method to be used are specified using the @dataProvider annotation.
Data provider methods must be public and must either return an array of arrays or an object which should implement the Iterator interface yielding an array for each iteration step.
Whenever a test method is called, the contents of arrays which are part of the collections are passed as arguments to the test method being called.
The example below illustrates how a data provider method returns an array of arrays.
<?php
use PHPUnit\Framework\TestCase;
class DataTest extends TestCase
{
/**
* @dataProvider additionProvider
*/
public function testAdd($a, $b, $expected)
{
$this->assertSame($expected, $a + $b);
}
public function additionProvider()
{
return [
[0, 0, 0],
[0, 1, 1],
[1, 0, 1],
[1, 1, 3]
];
}
}
?>
When the above test runs, the result shown below is produced
$ phpunit DataTest
PHPUnit |version|.0 by Sebastian Bergmann and contributors.
...F
Time: 0 seconds, Memory: 5.75Mb
There was 1 failure:
1) DataTest::testAdd with data set #3 (1, 1, 3)
Failed asserting that 2 is identical to 3.
/home/sb/DataTest.php:9
FAILURES!
Tests: 4, Assertions: 4, Failures: 1.
The results generated above asserts that three out of the four arrays tested failed the tests given.
When a large number of datasets is tested, it is intuitively needful to give each array a name instead of the default numeric number. The output will be easily understood as it will contain the name given should any of the datasets fail the test.
The example below illustrates a data provider with named datasets
<?php
use PHPUnit\Framework\TestCase;
class DataTest extends TestCase
{
/**
* @dataProvider additionProvider
*/
public function testAdd($a, $b, $expected)
{
$this->assertSame($expected, $a + $b);
}
public function additionProvider()
{
return [
'adding zeros' => [0, 0, 0],
'zero plus one' => [0, 1, 1],
'one plus zero' => [1, 0, 1],
'one plus one' => [1, 1, 3]
];
}
}
?>
The PHPUnit test above illustrating named datasets produces the following results when executed.
$ phpunit DataTest
PHPUnit |version|.0 by Sebastian Bergmann and contributors.
...F
Time: 0 seconds, Memory: 5.75Mb
There was 1 failure:
1) DataTest::testAdd with data set "one plus one" (1, 1, 3)
Failed asserting that 2 is identical to 3.
/home/sb/DataTest.php:9
FAILURES!
Tests: 4, Assertions: 4, Failures: 1.
From the output above, the failed dataset has a name attached to it, making it easier for the program to identify and fix the error.
As mentioned earlier, a data provider can either return an array of an array or an object implementing the iterator interface. The example below, illustrates a data provider, returning an iterator object.
<?php
use PHPUnit\Framework\TestCase;
require 'CsvFileIterator.php';
class DataTest extends TestCase
{
/**
* @dataProvider additionProvider
*/
public function testAdd($a, $b, $expected)
{
$this->assertSame($expected, $a + $b);
}
public function additionProvider()
{
return new CsvFileIterator.php('data.csv');
}
}
?>
The above date provider returning an iterator object, produces the following results on test.
$ phpunit DataTest
PHPUnit |version|.0 by Sebastian Bergmann and contributors.
...F
Time: 0 seconds, Memory: 5.75Mb
There was 1 failure:
1) DataTest::testAdd with data set #3 ('1', '1', '3')
Failed asserting that 2 is identical to 3.
/home/sb/DataTest.php:11
FAILURES!
Tests: 4, Assertions: 4, Failures: 1.
The CsvFileIterator which was required and tested in the above is defined below:
<?php
use PHPUnit\Framework\TestCase;
class CsvFileIterator implements Iterator
{
protected $file;
protected $key = 0;
protected $current;
public function __construct($file)
{
$this->file = fopen($file, 'r');
}
public function __destruct()
{
fclose($this->file);
}
public function rewind()
{
rewind($this->file);
$this->current = fgetcsv($this->file);
$this->key = 0;
}
public function valid()
{
return !feof($this->file);
}
public function key()
{
return $this->key;
}
public function current()
{
return $this->current;
}
public function next()
{
$this->current = fgetcsv($this->file);
$this->key++;
}
}
?>
Whenever a test accepts an argument from both a @dataProvider method and from one or more tests it @depends on, the arguments from the data providers will come before the ones from depended-upon tests.
The arguments from depended-upon tests will also be the same for each data set. The example below illustrates a test with a @dataProvider and @depends.
<?php
use PHPUnit\Framework\TestCase;
class DependencyAndDataProviderComboTest extends TestCase
{
public function provider()
{
return [['provider1'], ['provider2']];
}
public function testProducerFirst()
{
$this->assertTrue(true);
return 'first';
}
public function testProducerSecond()
{
$this->assertTrue(true);
return 'second';
}
/**
* @depends testProducerFirst
* @depends testProducerSecond
* @dataProvider provider
*/
public function testConsumer()
{
$this->assertSame(
['provider1', 'first', 'second'],
func_get_args()
);
}
}
?>
When the above test is executed, the results below are obtained.
$ phpunit --verbose DependencyAndDataProviderComboTest
PHPUnit |version|.0 by Sebastian Bergmann and contributors.
...F
Time: 0 seconds, Memory: 3.50Mb
There was 1 failure:
1) DependencyAndDataProviderComboTest::testConsumer with data set #1 ('provider2')
Failed asserting that two arrays are identical.
--- Expected
+++ Actual
@@ @@
Array &0 (
- 0 => 'provider1'
+ 0 => 'provider2'
1 => 'first'
2 => 'second'
)
/home/sb/DependencyAndDataProviderComboTest.php:32
FAILURES!
Tests: 4, Assertions: 4, Failures: 1.
Multiple data providers can also be used in a single test, the example below illustrates the use of multiple data providers in a single test.
<?php
use PHPUnit\Framework\TestCase;
class DataTest extends TestCase
{
/**
* @dataProvider additionWithNonNegativeNumbersProvider
* @dataProvider additionWithNegativeNumbersProvider
*/
public function testAdd($a, $b, $expected)
{
$this->assertSame($expected, $a + $b);
}
public function additionWithNonNegativeNumbersProvider()
{
return [
[0, 1, 1],
[1, 0, 1],
[1, 1, 3]
];
}
public function additionWithNegativeNumbersProvider()
{
return [
[-1, 1, 0],
[-1, -1, -2],
[1, -1, 0]
];
}
}
?>
The above test illustrating the use of multiple dependencies gives the following output.
$ phpunit DataTest
PHPUnit |version|.0 by Sebastian Bergmann and contributors.
..F... 6 / 6 (100%)
Time: 0 seconds, Memory: 5.75Mb
There was 1 failure:
1) DataTest::testAdd with data set #3 (1, 1, 3)
Failed asserting that 2 is identical to 3.
/home/sb/DataTest.php:12
FAILURES!
Tests: 6, Assertions: 6, Failures: 1.
It should be noted that when a test depends on a test that used data providers, the depending test can only be executed if only if at least one data set is successful. Also, the result of a test that uses data providers cannot be used in a depending test.
It is also worth noting that in the execution, all the data providers have executed before the call of the setUpBeforeClass() static method and the first call to the setUp() method. Because of this accessing the variables created within the data provider is not possible. This is so, because it is required in order for PHPunit to compute the total number of tests.
Previous:
Step-by-Step Guide to Installing PHPUnit.
Next:
Testing Output and Error Messages in PHPUnit.
It will be nice if you may share this link in any developer community or anywhere else, from where other developers may find this content. Thanks.
https://www.w3resource.com/php/PHPUnit/writing-tests-for-phpunit-data-providers.php
- Weekly Trends and Language Statistics
- Weekly Trends and Language Statistics