Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Filtering Out Hungry Dino's

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Instead of seeing our dinos on the dashboard, we're seeing a TypeError for GithubService:

Return value must be of type HealthStatus, null returned

That's not doing a great job of telling us what the problem really is. Thanks to the stack trace, it looks like it's being caused by a Status: Hungry label. Yup! On GitHub, it looks like Dennis is hungry again after finishing his daily exercise routine.

Our Enum Is Hungry Too

Looking at HealthStatus, we don't have a case for hungry dinos:

... lines 1 - 2
namespace App\Enum;
enum HealthStatus: string
{
case HEALTHY = 'Healthy';
case SICK = 'Sick';
}

So add case HUNGRY that returns Hungry... then refresh the dashboard.

... lines 1 - 2
namespace App\Enum;
enum HealthStatus: string
{
case HEALTHY = 'Healthy';
case SICK = 'Sick';
case HUNGRY = 'Hungry';
}

And... Ya! No more errors...

But, wait... It says that Dennis is not accepting visitors. He isn't sick, just hungry. GenLab said only sick dino's should not be on exhibit. Besides, who doesn't want to see what happens to the goat?

Test Hungry Dinos Can Have Visitors

In DinosaurTest, we need to assert that hungry dino's can have visitors. Hmm... I think we might be able to use testIsNotAcceptingVisitorsIfSick() for this. Yup, that's what we'll do. Below, add a healthStatusProvider() that returns \Generator and for the first dataset yield 'Sick dino is not accepting visitors'. In the array say HealthStatus::SICK, and false. Next, yield 'Hungry dino is accepting visitors' with [HealthStatus::HUNGRY, true]:

... lines 1 - 8
class DinosaurTest extends TestCase
{
... lines 11 - 58
public function healthStatusProvider(): \Generator
{
yield 'Sick dino is not accepting visitors' => [HealthStatus::SICK, false];
yield 'Hungry dino is accepting visitors' => [HealthStatus::HUNGRY, true];
}
}

Above, add the @dataProvider annotation so we can use healthStatusProvider(). While we're here, rename the method to testIsAcceptingVisitorsBasedOnHealthStatus then add the arguments HealthStatus $healthStatus and bool $expectedVisitorStatus:

... lines 1 - 8
class DinosaurTest extends TestCase
{
... lines 11 - 49
/**
* @dataProvider healthStatusProvider
*/
public function testIsAcceptingVisitorsBasedOnHealthStatus(HealthStatus $healthStatus, bool $expectedVisitorStatus): void
{
... lines 55 - 59
}
public function healthStatusProvider(): \Generator
{
yield 'Sick dino is not accepting visitors' => [HealthStatus::SICK, false];
yield 'Hungry dino is accepting visitors' => [HealthStatus::HUNGRY, true];
}
}

Inside set the health with $healthStatus then replace assertFalse() with assertSame($expectedStatus) is identical to $dino->isAcceptingVisitors():

... lines 1 - 8
class DinosaurTest extends TestCase
{
... lines 11 - 49
/**
* @dataProvider healthStatusProvider
*/
public function testIsAcceptingVisitorsBasedOnHealthStatus(HealthStatus $healthStatus, bool $expectedVisitorStatus): void
{
... lines 55 - 56
$dino->setHealth($healthStatus);
self::assertSame($expectedVisitorStatus, $dino->isAcceptingVisitors());
}
public function healthStatusProvider(): \Generator
{
yield 'Sick dino is not accepting visitors' => [HealthStatus::SICK, false];
yield 'Hungry dino is accepting visitors' => [HealthStatus::HUNGRY, true];
}
}

Phew, that was a lot of work!

Filtering Tests

Let's see if that did the trick. Run:

./vendor/bin/phpunit --filter testIsAcceptingVisitorsBasedOnHealthStatus

See what I did there? To focus on just this test, we can add the --filter set to the complete or partial name of a test class, method, or anything in between. This comes in really handy when you have a large test suite and only want to run one or a few tests.

Anywho, Hungry dino is not accepting visitors is failing:

Failed asserting that false is true.

Looking at Dinosaur::isAcceptingVisitors(), to account for hungry dino's, we need to return $this->health does not equal HealthStatus::SICK:

... lines 1 - 6
class Dinosaur
{
... lines 9 - 55
public function isAcceptingVisitors(): bool
{
return $this->health !== HealthStatus::SICK;
}
... lines 60 - 64
}

Let's see what happens when we run:

./vendor/bin/phpunit --filter "Hungry dino is accepting visitors"

And... boom! Our hungry dino test is now passing, ha! Yup, we can use data provider keys with the filter flag too. But to make sure we didn't stop healthy dino's from having visitors, run:

./vendor/bin/phpunit

Um... Yes! All dots and no errors. Shweet! We didn't wreck the park. Take a look at the dashboard, refresh, and ya! Dennis is able to eat his lunch with park guests once again. Though, I think we should be proactive and throw a more clear exception in case we ever see any future status labels that we don't know about. Let's do that next.

Leave a comment!

2
Login or Register to join the conversation
Jesus Avatar
Jesus Avatar Jesus | posted 4 months ago | edited

Hi, one question.

When we rename de method to testIsAcceptingVisitorsBasedOnHealthStatus() I think the data provider must add

 yield 'Healthy dino is accepting visitors' => [HealthStatus::Healthy,true];

¿Why? Because with this state is accepting visitors (as says de method)

Reply

Hey Jesus,

Actually, probably the reverse :) We do want to test cases with HealthStatus::SICK and HealthStatus::HUNGRY here, so probably the better test name would be to testIsNotAcceptingVisitorsBasedOnHealthStatus()... but it's minor and the current name technically also good because we made this test name kind of generic, i.e. saying in general here. The actual "value" of the expected visitor status should is passed as the 2nd argument and either true or false base on the given status.

I hope this clarify things for you :)

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// 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
    }
}
userVoice