Twelve near-identical test methods differing only in their input is twelve places to update when the method signature changes, and a suite nobody reads.
/**
* @dataProvider vatRates
*/
public function testVatIsApplied(string $country, int $net, int $gross): void
{
$this->assertSame($gross, (new Vat())->gross($net, $country));
}
public function vatRates(): array
{
return [
'uk standard' => ['GB', 1000, 1200],
'uk zero rated' => ['GB-Z', 1000, 1000],
'ie standard' => ['IE', 1000, 1230],
];
}
Naming the rows with string keys is what makes a failure readable — without them the report says “with data set #3” and you count. The provider runs before any setUp, which catches people expecting to use a fixture in it; anything needing the container has to be built inside the test. A provider returning a generator rather than an array is worth it for large sets, since the whole array is otherwise held in memory for the duration.