Buy Access to Course
12.

Mocking: Mock Objects

|

Share this awesome video!

|

Keep on Learning!

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

Login Subscribe

Our tests are passing, the dino's are wandering, and life is great! But... let's think about this for a second. In GithubService, when we test getHealthReport(), we're able to control the $response that we get back from request() by using a stub. That's great, but it might also be nice to ensure that the service is only calling GitHub one time and that it's using the right HTTP method with the correct URL. Could we do that? Absolutely!

Expect a Method to Be Called

In GithubServiceTest where we configure the $mockHttpClient, add ->expects(), and pass self::once().

61 lines | tests/Unit/Service/GithubServiceTest.php
// ... lines 1 - 11
class GithubServiceTest extends TestCase
{
// ... lines 14 - 16
public function testGetHealthReportReturnsCorrectHealthStatusForDino(HealthStatus $expectedStatus, string $dinoName): void
{
// ... lines 19 - 36
$mockHttpClient
->expects(self::once())
// ... lines 39 - 40
;
// ... lines 42 - 45
}
// ... lines 47 - 59
}

Over in the terminal, run our tests...

./vendor/bin/phpunit

Expecting Specific Arguments

And... Awesome! We've just added an assertion to our mock client that requires the request method be called exactly once. Let's take it a step further and add ->with() passing GET... and then I'll paste the URL to the GitHub API.

62 lines | tests/Unit/Service/GithubServiceTest.php
// ... lines 1 - 11
class GithubServiceTest extends TestCase
{
// ... lines 14 - 16
public function testGetHealthReportReturnsCorrectHealthStatusForDino(HealthStatus $expectedStatus, string $dinoName): void
{
// ... lines 19 - 36
$mockHttpClient
->expects(self::once())
->method('request')
->with('GET', 'https://api.github.com/repos/SymfonyCasts/dino-park')
->willReturn($mockResponse)
;
// ... lines 43 - 46
}
// ... lines 48 - 60
}

Try the tests again...

./vendor/bin/phpunit

And... Huh! We have 2 failures:

Failed asserting that two strings are equal

Hmm... Ah Ha! My copy and paste skills are a bit weak. I missed /issue at the end of the URL. Add that.

62 lines | tests/Unit/Service/GithubServiceTest.php
// ... lines 1 - 11
class GithubServiceTest extends TestCase
{
// ... lines 14 - 16
public function testGetHealthReportReturnsCorrectHealthStatusForDino(HealthStatus $expectedStatus, string $dinoName): void
{
// ... lines 19 - 36
$mockHttpClient
// ... lines 38 - 39
->with('GET', 'https://api.github.com/repos/SymfonyCasts/dino-park/issues')
// ... line 41
;
// ... lines 43 - 46
}
// ... lines 48 - 60
}

Let's see if that was the trick:

./vendor/bin/phpunit

Umm... Yes! We're green all day. But best of all, the tests confirm we're using the correct URL and HTTP method when we call GitHub.

But... What if we actually wanted to call GitHub more than just once? Or... we wanted to assert that it was not called at all? PHPUnit has us covered. There are a handful of other methods we can call. For example, change once() to never().

And watch what happens now:

./vendor/bin/phpunit

Hmm... Yup, we have two failures because:

request() was not expected to be called.

That's really nifty! Change the expects() back to once() and just to be sure we didn't break anything - run the tests again.

./vendor/bin/phpunit

And... Awesome!

Carefully Applying Assertions

We could call expects() on our $mockResponse to make sure that toArray() is being called exactly once in our service. But, do we really care? If it's not being called at all, our test would certainly fail. And if it's being called twice, no big deal! Using ->expects() and ->with() are great ways to add extra assertions... when you need them. But no need to micromanage how many times something is called or its arguments if that is not so important.

Using GitHubService in our App

Now that GithubService is fully tested, we can celebrate by using it to drive our dashboard! On MainController::index(), add an argument: GithubService $github to autowire the new service.

33 lines | src/Controller/MainController.php
// ... lines 1 - 5
use App\Service\GithubService;
// ... lines 7 - 10
class MainController extends AbstractController
{
#[Route(path: '/', name: 'main_controller', methods: ['GET'])]
public function index(GithubService $github): Response
{
// ... lines 16 - 30
}
}

Next, right below the $dinos array, foreach() over $dinos as $dino and, inside say $dino->setHealth() passing $github->getHealthReport($dino->getName()).

33 lines | src/Controller/MainController.php
// ... lines 1 - 5
use App\Service\GithubService;
// ... lines 7 - 10
class MainController extends AbstractController
{
#[Route(path: '/', name: 'main_controller', methods: ['GET'])]
public function index(GithubService $github): Response
{
// ... lines 16 - 23
foreach ($dinos as $dino) {
$dino->setHealth($github->getHealthReport($dino->getName()));
}
// ... lines 27 - 30
}
}

To the browser and refresh...

And... What!

getDinoStatusFromLabels() must be HealthStatus, null returned

What's going on here? By the way, the fact that our unit test passes but our page fails can sometimes happen and in a future tutorial, we'll write a functional test to make sure this page actually loads.

The error isn't very obvious, but I think one of our dino's has a status label that we don't know about. Let's peek back at the issues on GitHub and... HA! "Dennis" is causing problems yet again. Apparently he's a bit hungry...

In our HealthStatus enum, we don't have a case for Hungry status labels. Go figure. Is a hungry dinosaur accepting visitors? I don't know - I guess it depends on if you ask the visitor or the dino. Anyways, Hungry is not a status we expected. So next, let's throw a clear exception if we run into an unknown status and test for that exception.