If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeWe treat our source code as a first-class citizen. That means, among other things, we avoid duplication. Why not do the same with our tests? Our three tests for the size are... repetitive. They test the same thing just with slightly different input and then a different assertion. Is there a way to improve this? Absolutely: thanks to PHPUnit Data Providers.
Move to the bottom of DinosaurTest
and add public function sizeDescriptionProvider()
. Inside, yield
an array with [10, 'Large']
, then yield [5, 'Medium']
, and finally yield [4,
'Small']:
... lines 1 - 7 | |
class DinosaurTest extends TestCase | |
{ | |
... lines 10 - 31 | |
public function sizeDescriptionProvider() | |
{ | |
yield [10, 'Large']; | |
yield [5, 'Medium']; | |
yield [4, 'Small']; | |
} | |
} |
Yield is just a fancy way of returning arrays using PHP's built-in Generator function. As you'll see in a minute, these values - like 10
and large
will become arguments to our test.
Alrighty, up in our test method, add an int $length
argument and then string $expectedSize
:
... lines 1 - 7 | |
class DinosaurTest extends TestCase | |
{ | |
... lines 10 - 27 | |
public function testDino10MetersOrGreaterIsLarge(int $length, string $expectedSize): void | |
{ | |
... lines 30 - 32 | |
} | |
... lines 34 - 40 | |
} |
Now instead of Big Eaty's length being 10
, use $length
. And for our assertion, use $expectedSize
instead of Large
:
... lines 1 - 7 | |
class DinosaurTest extends TestCase | |
{ | |
... lines 10 - 27 | |
public function testDino10MetersOrGreaterIsLarge(int $length, string $expectedSize): void | |
{ | |
$dino = new Dinosaur(name: 'Big Eaty', length: $length); | |
self::assertSame($expectedSize, $dino->getSizeDescription(), 'This is supposed to be a large Dinosaur'); | |
} | |
... lines 34 - 40 | |
} |
We do not need the medium and small tests anymore, so we can remove both of them.
Ok! Move back to your terminal and run our tests:
./vendor/bin/phpunit --testdox
Uh oh... Our test is failing because! It says:
ArgumentCountError - Too few arguments were provided. 0 passed and exactly 2 expected.
Oops, we never told our test method to use the data provider. Move back into our test and add a DocBlock with @dataProvider sizeDescriptionProvider
:
... lines 1 - 7 | |
class DinosaurTest extends TestCase | |
{ | |
... lines 10 - 24 | |
/** | |
* @dataProvider sizeDescriptionProvider | |
*/ | |
public function testDino10MetersOrGreaterIsLarge(int $length, string $expectedSize): void | |
{ | |
$dino = new Dinosaur(name: 'Big Eaty', length: $length); | |
self::assertSame($expectedSize, $dino->getSizeDescription(), 'This is supposed to be a large Dinosaur'); | |
} | |
... lines 34 - 40 | |
} |
When PHPUnit 10 gets released, we'll be able to use a fancy #[DataProvider]
attribute instead of this annotation.
Back to the terminal! Run the tests again:
./vendor/bin/phpunit --testdox
And... Yes! Our tests are passing!
In the output, we see that each test ran with datasets 0, 1, & 2. Those are the arrays from the data provider. We can spruce this up a bit... because it's not going to be very helpful later if PHPUnit tells us that dataset 2
failed. Which one is that?
Move back to our test and, down here after the first yield
statement, add the message key '10 Meter Large Dino' =>
. Copy and paste this for our medium dino with 5
instead of 10
and this needs to be Medium
. Do the same for our small dino with 4
and Small
:
... lines 1 - 7 | |
class DinosaurTest extends TestCase | |
{ | |
... lines 10 - 34 | |
public function sizeDescriptionProvider() | |
{ | |
yield '10 Meter Large Dino' => [10, 'Large']; | |
yield '5 Meter Medium Dino' => [5, 'Medium']; | |
yield '4 Meter Small Dino' => [4, 'Small']; | |
} | |
} |
Back in our terminal, let's see our tests now:
./vendor/bin/phpunit --testdox
And... Cool Beans! We now have
Dino 10 meters or greater is large with 10 Meter Large Dino
This looks a lot better than just seeing data set 0... though we do need to fix one more thing. That test method name doesn't make sense anymore. Change it to testDinoHasCorrectSizeDescriptionFromLength()
.
And, looking at our assertion, the message argument isn't very useful anymore... so let's remove it.
... lines 1 - 7 | |
class DinosaurTest extends TestCase | |
{ | |
... lines 10 - 34 | |
public function sizeDescriptionProvider() | |
{ | |
yield '10 Meter Large Dino' => [10, 'Large']; | |
yield '5 Meter Medium Dino' => [5, 'Medium']; | |
yield '4 Meter Small Dino' => [4, 'Small']; | |
} | |
} |
Finally, although not required... We can use either array
or \Generator
as the return type for the data provider. Let's go with \Generator
- after all, we may need those for the park fences one day...
... lines 1 - 7 | |
class DinosaurTest extends TestCase | |
{ | |
... lines 10 - 34 | |
public function sizeDescriptionProvider(): \Generator | |
{ | |
... lines 37 - 39 | |
} | |
} |
To make sure this didn't break anything, try the tests one more time:
./vendor/bin/phpunit --testdox
Ummm... Awesome! Green Checks Everywhere!
And there you have it, with a little TLC, our tests are now nice and tidy... Coming up next, let's figure out how we can get our Dino's health status from GitHub and use it in our app...
Hey Lechu,
I think PHPUnit does not support PHP attributes yet, you need to use annotations instead
Cheers!
Howdy! Thanks for pointing that out to us. It does indeed seam slightly off. We'll look into it and see what we can come up with.
// composer.json
{
"require": {
"php": ">=8.1.0",
"ext-ctype": "*",
"ext-iconv": "*",
"symfony/asset": "6.1.*", // v6.1.0
"symfony/console": "6.1.*", // v6.1.4
"symfony/dotenv": "6.1.*", // v6.1.0
"symfony/flex": "^2", // v2.2.3
"symfony/framework-bundle": "6.1.*", // v6.1.4
"symfony/http-client": "6.1.*", // v6.1.4
"symfony/runtime": "6.1.*", // v6.1.3
"symfony/twig-bundle": "6.1.*", // v6.1.1
"symfony/yaml": "6.1.*" // v6.1.4
},
"require-dev": {
"phpunit/phpunit": "^9.5", // 9.5.23
"symfony/browser-kit": "6.1.*", // v6.1.3
"symfony/css-selector": "6.1.*", // v6.1.3
"symfony/phpunit-bridge": "^6.1" // v6.1.3
}
}
Data provider attributes, doesn't work :/
When I use: #[DataProvider('sizeDescriptionProvider')]
PhpStorm shows me "Undefined class 'DataProvider'" :)