When I built a Salary Calculator for Germany using Laravel, I started covering the code with Unit Tests using PHPUnit. This particularly came in handy when I had to cover more edge cases as I was able to trust the calculation. One of the features that I didn’t know until that point was Data Providers in PHPUnit.
What is a Data Provider?
When I write a test for a method, or a class, I write multiple assertions with the values for the same code. Data Providers are useful here when all that changes is the values passed to the method.
DataProviders are a framework for easily controlling how data can be provided from a source
Let’s take an example of how you would write a test without data providers. Assume you have a method add($a, $b)
in a class Operations
that returns a value, summing up the numbers.
public function test_add()
{
$actual = (new Operations)->add(1, 2);
$this->assertEquals(3, $actual);
}
When you want to add multiple assertions, you would be tempted to add another assertion like this:
public function test_add()
{
$actual = (new Operations)->add(1, 2);
$this->assertEquals(3, $actual);
$actual = (new Operations)->add(3, 4);
$this->assertEquals(7, $actual);
}
// Or a test per method
public function test_add_first()
{
$actual = (new Operations)->add(1, 2);
$this->assertEquals(3, $actual);
}
public function test_add_second()
{
$actual = (new Operations)->add(3, 4);
$this->assertEquals(7, $actual);
}
These add overhead if the signature of the method changes, or when you want to cover more cases.
How to use a Data Provider in PHPUnit
A Data Provider can simplify this, allowing you to pass multiple sets of values for the same test. From the official documentation:
A data provider method
- must be
public
- and either return an array of arrays or an object that implements the
Iterator
interface and yields an array for each iteration step.
Let’s convert the existing test case using a Data Provider. We will use a dataProvider
annotation following the name of the data provider.
/**
* @dataProvider addProvider
*/
public function test_add($a, $b, $expected)
{
$actual = (new Operations)->add($a, $b);
$this->assertEquals($expected, $actual);
}
public function addProvider()
{
return array(
array(1, 2, 3),
array(4, 5, 9),
);
}
In this case, PHPUnit would pass the take the array, loop through the items (array again) and pass the arguments based on the position of the method parameters.
For the first item, it will take array(1, 2, 3)
, call test_add(1, 2, 3)
and validate the assertions. Then the next item and so on.
Inline Data Providers in PHPUnit
In the test cases above with a Data Provider, we can simplify it even further by inlining the data provider. The annotation to use is testWith
/**
* @testWith array(
array(1, 2, 3),
array(4, 5, 9),
);
*/
public function test_add($a, $b, $expected)
{
$actual = (new Operations)->add($a, $b);
$this->assertEquals($expected, $actual);
}