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 SubscribeNow that we can see if a Dinosaur
is accepting visitors on our dashboard, we need to keep the dashboard updated in real-time by using the health status labels that GenLab has applied to several dino issues on GitHub. To do that we'll create a service that will grab those labels using GitHub's API.
To test our new service... which doesn't exist yet, inside of tests/Unit/
create a new Service/
directory and then a new class: GithubServiceTest
... which will extend TestCase
:
... lines 1 - 3 | |
<div class="container volcano mt-4" style="flex-grow: 1;"> | |
... line 5 | |
<div class="dino-stats-container mt-2 p-3"> | |
<table class="table table-striped"> | |
<thead> | |
<tr> | |
... lines 10 - 13 | |
<th>Accepting Visitors</th> | |
</tr> | |
</thead> | |
<tbody> | |
{% for dino in dinos %} | |
<tr> | |
... lines 20 - 23 | |
<td>{{ dino.acceptingVisitors ? 'Yes' : 'No' }}</td> | |
</tr> | |
{% endfor %} | |
</tbody> | |
</table> | |
</div> | |
</div> | |
... lines 31 - 53 |
I'm creating this in a Service/
sub-directory because I'm planning to put the class in the src/Service/
directory. Add method testGetHealthReportReturnsCorrectHealthStatusForDino
and inside, $service = new GithubService()
. Yup, that doesn't exist yet either...
Our service will return a HealthStatus
enum that's created from the health status label on GitHub, so we'll assertSame()
that $expectedStatus
is identical to $service->getHealthReport()
and then pass $dinoName
. Yup, we'll be using a data provider for this test... where we accept the name of the dino to check for their expected health status.
Let's go create that: public function dinoNameProvider()
that returns a \Generator
. Our first dataset for the provider will have the key Sick Dino
, which returns an array with HealthStatus::SICK
and Daisy
for the dino's name... because when we checked GitHub a minute ago, Daisy was sick!
Next up is a Healthy Dino
with HealthStatus::HEALTHY
who happens to be the one and only Maverick
. Up on the test method, add a @dataProvider
annotation so the test uses dinoNameProvider
... and then add HealthStatus $expectedStatus
and string $dinoName
arguments.
... lines 1 - 2 | |
namespace App\Tests\Unit\Service; | |
use App\Enum\HealthStatus; | |
use PHPUnit\Framework\TestCase; | |
class GithubServiceTest extends TestCase | |
{ | |
/** | |
* @dataProvider dinoNameProvider | |
*/ | |
public function testGetHealthReportReturnsCorrectHealthStatusForDino(HealthStatus $expectedStatus, string $dinoName): void | |
{ | |
$service = new GithubService(); | |
self::assertSame($expectedStatus, $service->getHealthReport($dinoName)); | |
} | |
public function dinoNameProvider(): \Generator | |
{ | |
yield 'Sick Dino' => [ | |
HealthStatus::SICK, | |
'Daisy', | |
]; | |
yield 'Healthy Dino' => [ | |
HealthStatus::HEALTHY, | |
'Maverick', | |
]; | |
} | |
} |
Let's do this! Find your terminal and run:
./vendor/bin/phpunit
And... Yup! Just as we expected, we have two errors because:
The GithubService class cannot be found
To fix that, find a teammate and ask them nicely to create this class for you! TDD - team-driven-development!
I'm kidding: we got this! Inside of src/
, create a new Service/
directory. Then we'll need the new class: GithubService
and inside, add a method: getHealthReport()
which takes a string $dinosaurName
and gives back a HealthStatus
object.
... lines 1 - 2 | |
namespace App\Service; | |
use App\Enum\HealthStatus; | |
class GithubService | |
{ | |
public function getHealthReport(string $dinosaurName): HealthStatus | |
{ | |
... lines 11 - 15 | |
} | |
} |
Here's the plan: we'll call GitHub's API to get the list of issues for the dino-park
repository. Then we'll filter those issues to pick the one that matches $dinosaurName
. Finally, we'll return HealthStatus::HEALTHY
, unless the issue has a Status: Sick
label.
Before we dive into writing that method, jump back into our test and chop off the last couple of letters for GithubService
. With a little PHPStorm Magic... as soon as I type the letter i
and hit enter, the use statement is automatically added to the test. Thank you JetBrains!
... lines 1 - 5 | |
use App\Service\GithubService; | |
... lines 7 - 8 | |
class GithubServiceTest extends TestCase | |
{ | |
... lines 11 - 13 | |
public function testGetHealthReportReturnsCorrectHealthStatusForDino(HealthStatus $expectedStatus, string $dinoName): void | |
{ | |
$service = new GithubService(); | |
... lines 17 - 18 | |
} | |
... lines 20 - 32 | |
} |
Let's see how the tests are looking:
./vendor/bin/phpunit
And... Ha! Instead of two failures, we now only have one...
Sick Dino failed asserting that the two variables reference the same object.
Coming up next, we'll add some logic to our GithubService
to make this test pass!
"Houston: no signs of life"
Start the conversation!
// 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
}
}