Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

KernelTestCase: Fetching Services

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

In our app, if we wanted to use LockDownRepository to make some real queries, we could autowire LockDownRepository into a controller - or somewhere else - call a method on it, and boom! Everything would work.

Now we want to do the same thing in our test: instead of creating the object manually, we want to ask Symfony to give us the real service that's configured to talk to the real database, so it can do its real logic. Really!

Booting the Kernel

To fetch a service inside a test, we need to boot up Symfony then get access to its service container: the mystical object that holds every service in our app.

To help with this, Symfony gives us a base class called KernelTestCase. There's nothing particularly special about this class. Hold "command" or "control" to see that it extends the normal TestCase from PHPUnit. It just adds methods to boot and shut down Symfony's kernel - that's kind of the heart of Symfony - and to grab the container.

... lines 1 - 4
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
... line 6
class LockDownRepositoryTest extends KernelTestCase
... lines 8 - 14

Fetching Services

At the top of our test method, start with self::bootKernel(). Once you call this, you can imagine that you have a Symfony app running in the background, waiting for you to use it. Specifically, this means we can grab any service. Do that with $lockDownRepository = self::getContainer() (which is a helper method from KernelTestCase) ->get(). Then pass the service ID which, in our case, is the class name: LockDownRepository::class.

To see if this works, dd($lockDownRepository).

... lines 1 - 9
public function testIsInLockDownWithNoLockDownRows()
$lockDownRepository = self::getContainer()->get(LockDownRepository::class);
... lines 17 - 18

By the way, unit tests and integration tests generally look the same: you call methods on an object and run assertions. If your test happens to boot the kernel and grab a real service, we give it the name "integration test". But that's just a fancy way of saying: "A unit test... except we use real services".

Okay, let's try this! At your terminal, run:


You can also run ./bin/phpunit - which is a shortcut set up for Symfony. But I'll stick to running phpunit directly.

And... yes! There's our service! It doesn't look like much, but this lazy object is something that lives in the real service.

The Special Test Service Container

So, simple! self::getContainer gives us the service container... and then we call get() on it. But I do want to point out that accessing the service container and grabbing a service from it is not something we do in our application code. For most services, which are private, doing this won't even work! Instead, we rely on dependency injection and autowiring.

But in a test, there is no dependency injection or autowiring. So, we need to grab services like this. And the only reason this even works is because self::getContainer() gives us a special container that only exists in the test environment. It's special because it does allow you to call a get() method and ask for any service you want by its ID... even if that service is normally private. So this is a unique superpower to the test environment.

Running Code & Asserting

Ok, since we have LockDownRepository, let's try running a simple test. But, hmm, I'm not getting the right autocompletion. Ah, that's because my editor doesn't know what the get() method returns. To help it, assert() that $lockDownRepository is an instanceof LockDownRepository. This isn't a PHPUnit assertion: we didn't say $this->assert-something. This is just a PHP function that will throw an exception if $lockDownRepository is not a LockDownRepository. It will be... and this code will never cause a problem... but now we enjoy lovely autocompletion!

... lines 1 - 4
use App\Repository\LockDownRepository;
... lines 6 - 7
class LockDownRepositoryTest extends KernelTestCase
public function testIsInLockDownReturnsFalseWithNoRows()
... lines 12 - 14
assert($lockDownRepository instanceof LockDownRepository);
... line 16

Say $this->assertFalse($lockDownRepository->isInLockDown()).

... lines 1 - 9
public function testIsInLockDownReturnsFalseWithNoRows()
... lines 12 - 15
... lines 18 - 19

The idea is that we haven't added any rows to the database... and because of that, we should not be in a lockdown. And since the method just returns false right now... this test should pass:


And... it does! So we're using the real service... but it's not, yet, making any queries. Will this keep working if we do make a query? Let's find out, next.

Leave a comment!

Login or Register to join the conversation
Cat in space

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

This tutorial uses PHPUnit 9 but works just fine for PHPUnit 10.

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": ">=8.1.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "doctrine/doctrine-bundle": "^2.8", // 2.10.2
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.4
        "doctrine/orm": "^2.14", // 2.16.2
        "symfony/asset": "6.3.*", // v6.3.0
        "symfony/console": "6.3.*", // v6.3.4
        "symfony/dotenv": "6.3.*", // v6.3.0
        "symfony/flex": "^2", // v2.3.3
        "symfony/framework-bundle": "6.3.*", // v6.3.5
        "symfony/http-client": "6.3.*", // v6.3.5
        "symfony/mailer": "6.3.*", // v6.3.5
        "symfony/messenger": "6.3.*", // v6.3.5
        "symfony/monolog-bundle": "^3.0", // v3.8.0
        "symfony/runtime": "6.3.*", // v6.3.2
        "symfony/security-csrf": "6.3.*", // v6.3.2
        "symfony/twig-bundle": "6.3.*", // v6.3.0
        "symfony/yaml": "6.3.*" // v6.3.3
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.4
        "phpunit/phpunit": "^9.5", // 9.6.13
        "symfony/browser-kit": "6.3.*", // v6.3.2
        "symfony/css-selector": "6.3.*", // v6.3.2
        "symfony/debug-bundle": "6.3.*", // v6.3.2
        "symfony/maker-bundle": "^1.48", // v1.51.1
        "symfony/phpunit-bridge": "^6.2", // v6.3.2
        "symfony/stopwatch": "6.3.*", // v6.3.0
        "symfony/web-profiler-bundle": "6.3.*", // v6.3.2
        "zenstruck/foundry": "^1.35", // v1.35.0
        "zenstruck/mailer-test": "^1.3", // v1.3.0
        "zenstruck/messenger-test": "^1.7" // v1.7.3