Buy Access to Course
08.

The Repository Test Helper

|

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

All right, team! We've covered all the main parts of integration testing! Woo! And, it's delightfully simple: just a strategy to grab the real services from a container and test them, which... ultimately gives us a more realistic test.

The downsides of integration tests are that they run slower than unit tests, and they're often more complex... because we need to think about things like clearing and seeding the database. And sometimes, we don't want real things (like API calls) to happen. In this case, we can use a bit of mocking to avoid that. The big takeaway is, like everything, use the right tool - unit testing or integration testing - for the right job. That's situational and it's okay to use both.

As we near the finish line, let's dive into testing some of the trickier parts in our system: like whether emails were sent or messenger messages were dispatched. To do this, we need to give Bob a new superpower: the ability to put the park into lockdown. Once activated, our app will shoot off an email to the park crew, basically saying:

Alert! Dinosaurs on the loose!

Creating the Command

Head over to LockDownHelper. Down here, create a new method. We'll call this to put the park into lockdown, so how about public function dinoEscaped(). Give it a void return type and just put some TODO comments here outlining what we need to do: save a LockDown to the database and send an email.

38 lines | src/Service/LockDownHelper.php
// ... lines 1 - 8
class LockDownHelper
{
// ... lines 11 - 31
public function dinoEscaped(): void
{
// TODO: create a LockDown & save
// send an email with subject like "RUUUUUUNNNNNN!!!!"
}
}

To call this code and trigger the lockdown, let's create a new console command. At the terminal, run:

php bin/console make:command

Call it app:lockdown:start.

Simple enough! That created a single class in src/Command/LockdownStartCommand.php. Inside, autowire a private LockDownHelper $lockDownHelper and make sure to call the parent constructor.

39 lines | src/Command/LockdownStartCommand.php
// ... lines 1 - 4
use App\Service\LockDownHelper;
// ... lines 6 - 13
#[AsCommand(
name: 'app:lockdown:start',
description: 'Add a short description for your command',
)]
class LockdownStartCommand extends Command
{
public function __construct(private LockDownHelper $lockDownHelper)
{
parent::__construct();
}
// ... lines 24 - 37
}

Down here, delete pretty much all of this logic... and replace it with $this->lockDownHelper->dinoEscaped() and $io->caution('Lockdown started!!!!!!!!!!').

39 lines | src/Command/LockdownStartCommand.php
// ... lines 1 - 28
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$this->lockDownHelper->dinoEscaped();
$io->caution('Lockdown started!!!!!!');
return Command::SUCCESS;
}
// ... lines 38 - 39

Dangerous. This method doesn't do anything yet, but we can already go ahead and try the command. Copy its name... and run:

php bin/console app:lockdown:start

Love it!

Creating the Test

Before we get our hands dirty with the new method, let's write a test. But first, let's do that trick where we add a private function to help us get the service we're testing: private function getLockDownHelper(), which will return a LockDownHelper. Inside, copy the code from above... and return it. Then, simplify the code up here to just $this->getLockDownHelper()->endCurrentLockDown().

// ... lines 1 - 12
class LockDownHelperTest extends KernelTestCase
{
// ... lines 15 - 16
public function testEndCurrentLockdown()
{
// ... lines 19 - 29
$this->getLockDownHelper()->endCurrentLockDown();
// ... line 31
}
// ... line 33
private function getLockDownHelper(): LockDownHelper
{
return self::getContainer()->get(LockDownHelper::class);
}
}

All right, now create the new test method: public function testDinoEscapedPersistsLockDown(). Start the same way we always do - by booting the kernel. Then call the method with $this->getLockDownHelper()->dinoEscaped().

// ... lines 1 - 33
public function testDinoEscapedPersistsLockDown()
{
self::bootKernel();
$this->getLockDownHelper()->dinoEscaped();
}
// ... lines 40 - 46

Cool! It's not interesting, but try the test anyway:

symfony php vendor/bin/phpunit tests/Integration/Service/LockDownHelperTest.php

It doesn't fail, but... it is risky because we haven't performed any assertions.

Database Assertions via the Repository

What we want to assert is that this did insert a row into the database. To do that, we could grab the entity manager or our repository service, make a query, and do some assertions using that. However, Foundry comes with a nice trick for this.

After we call the method, say LockDownFactory. Normally, we would call things like create or createMany, but this also has a method on it named repository. This returns an object from Foundry that wraps the repository - much like how Foundry wraps our entities in a Proxy object. This means we can call real repository methods on it - like findMostRecent() or isInLockDown(). But it also has extra stuff, like assert(). Say ->assert()->count(1) to make sure that the there is one record in this table. We could go further and fetch that record to make sure its status is "active", but I'll skip that.

// ... lines 1 - 33
public function testDinoEscapedPersistsLockDown()
{
// ... lines 36 - 38
LockDownFactory::repository()->assert()->count(1);
}
// ... lines 41 - 47

Run the test now.

symfony php vendor/bin/phpunit tests/Integration/Service/LockDownHelperTest.php

This should fail and... it does.

I'll go paste in some code that creates the LockDown and saves it. Easy peasy boring code.

43 lines | src/Service/LockDownHelper.php
// ... lines 1 - 9
class LockDownHelper
// ... lines 11 - 32
public function dinoEscaped(): void
{
$lockDown = new LockDown();
$lockDown->setStatus(LockDownStatus::ACTIVE);
$lockDown->setReason('Dino escaped... NOT good...');
$this->entityManager->persist($lockDown);
$this->entityManager->flush();
// send an email with subject like "RUUUUUUNNNNNN!!!!"
}
}

Try the test now... it passes!

Next: let's send the email and test that it was sent. We'll do this with some core Symfony tools and also with another library from zenstruck.