Covering a rate table usually produces six test methods that differ in two literals each, so changing the assertion means changing it six times and one of them gets missed. @dataProvider moves the cases into a method that returns rows and leaves a single test that stops changing.
/**
* @dataProvider vatCases
*/
public function testGrossPrice($country, $net, $expected, $exception = null)
{
if ($exception !== null) {
$this->setExpectedException($exception);
}
$this->assertSame($expected, Vat::gross($country, $net));
}
public function vatCases()
{
return array(
'standard rate' => array('TR', 10000, 11800),
'zero rated' => array('TR', 0, 0),
'reduced rate' => array('TR', 5000, 5400, null),
'unknown country' => array('ZZ', 10000, null, 'InvalidArgumentException'),
);
}
Naming the rows with string keys is worth the extra characters: a failure then reports with data set "unknown country" instead of data set #3, which is the difference between reading the message and counting rows in the provider. The fourth column is the detail that catches people — @expectedException is an annotation on the method, so it applies to every row, and a table mixing valid and invalid input cannot use it; calling setExpectedException() from a column keeps both kinds of case in one table. Providers are also resolved while the suite is being built, before setUp() runs and before --filter is applied, so a provider that queries the database costs that query even on a run that excludes the test.