This course is still being released! Check back later for more chapters.

Get Notified About this Course!

We will send you messages regarding this course only
and nothing else, we promise.
You can unsubscribe anytime by emailing us at:
privacy@symfonycasts.com
Login to bookmark this video
Buy Access to Course
29.

Integration Testing

|

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

We have our bundle's unit tests working - now for something a bit more complicated: integration tests. These tests will use the full Symfony service container, just like in a real application. This allows us to test services and their interactions in a more realistic environment.

In our bundle's tests directory, next to the Unit directory, create a new Integration folder.

ObjectTranslatorTest

The main entry point for our bundle is this ObjectTranslator service, so it's a great candidate for our first integration test.

Inside this new directory, create a new PHP class called ObjectTranslatorTest.

We don't need to worry about marking as final or @internal for tests. They won't even be available when the bundle is used as a dependency.

Instead of extending from PHPUnit's TestCase, extend KernelTestCase:

// ... lines 1 - 4
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
// ... lines 6 - 7
class ObjectTranslatorTest extends KernelTestCase
{
// ... lines 10 - 15
}

This allows us to boot up the Symfony kernel and access the service container.

For our first test, let's start super simple: public function testCanAccessService():

// ... lines 1 - 4
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
// ... lines 6 - 7
class ObjectTranslatorTest extends KernelTestCase
{
public function testCanAccessService()
{
// ... lines 12 - 14
}
}

This will just be temporary test to ensure our integration test setup is working.

Inside, we'll grab a service I know every Symfony app has: the CacheInterface. Write: $cache = self::getContainer()->get(CacheInterface::class); and then $this->assertInstanceOf(CacheInterface::class, $cache);:

// ... lines 1 - 4
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
// ... lines 6 - 7
class ObjectTranslatorTest extends KernelTestCase
{
public function testCanAccessService()
{
$cache = self::getContainer()->get(CacheInterface::class);
$this->assertInstanceOf(CacheInterface::class, $cache);
}
}

If this assertion passes, our integration test setup is working!

Over in the terminal, navigate to the bundle directory and run:

symfony php vendor/bin/phpunit

Hmm... an error, and yep, it's from testCanAccessService. It's saying this KERNEL_CLASS environment variable needs to be set...

Ok, so for an integration test, we need a Symfony kernel. In our app we have a Kernel, but we can't use this in our bundle tests... We need to create a test-specific kernel!

TestKernel

In our bundle's tests directory, create a new folder named Fixture. Inside, create a new class named TestKernel that extends Symfony\Component\HttpKernel\Kernel and have it use MicroKernelTrait:

// ... lines 1 - 4
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
// ... lines 6 - 8
use Symfony\Component\HttpKernel\Kernel;
class TestKernel extends Kernel
{
use MicroKernelTrait;
// ... lines 14 - 20
}

Test Kernel done! Well... not quite. It needs some configuration. In real apps, we use YAML files, and you can totally do that here too. This Fixture directory is like a mini-app. To avoid having a bunch of extra files, we can add the configuration directly in our TestKernel class.

TestKernel Configuration

Drill into the MicroKernelTrait and find this configureContainer method. The trait's version looks for YAML files for configuration. We can override it to define configuration directly. Copy the method signature, and paste into our TestKernel, importing the necessary classes:

// ... lines 1 - 5
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
// ... line 9
class TestKernel extends Kernel
{
// ... lines 13 - 14
private function configureContainer(ContainerConfigurator $container, LoaderInterface $loader, ContainerBuilder $builder): void
{
// ... lines 17 - 19
}
}

We just need to configure the framework for testing. Write: $builder->loadFromExtension('framework');. This is the key you're used to seeing in your app's YAML configuration files. Then ['test' => true] to enable the testing mode:

// ... lines 1 - 10
class TestKernel extends Kernel
{
// ... lines 13 - 14
private function configureContainer(ContainerConfigurator $container, LoaderInterface $loader, ContainerBuilder $builder): void
{
$container->extension('framework', [
'test' => true,
]);
}
}

KERNEL_CLASS Environment Variable

Now we need to tell our integration tests about this TestKernel. The easiest way to do this is to set that KERNEL_CLASS environment variable the test error was complaining about.

Copy the namespace for TestKernel and open our bundle's phpunit.xml.dist file. Inside the <php> node, add another environment variable: <env name="KERNEL_CLASS" value="{paste the namespace we copied}/TestKernel" />:

33 lines | object-translation-bundle/phpunit.xml.dist
// ... lines 1 - 3
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
// ... lines 5 - 9
>
<php>
// ... lines 12 - 13
<env name="KERNEL_CLASS" value="SymfonyCasts\ObjectTranslationBundle\Tests\Fixture\TestKernel"/>
</php>
// ... lines 16 - 31
</phpunit>

Cool! Now this variable is available in our tests and Symfony's KernelTestCase will use it to boot up our TestKernel.

Back in the terminal, run the tests again:

symfony php vendor/bin/phpunit

Woo! All green! Our integration test is working!

Test Container Cache

Back in our IDE, in our bundle directory, we see a new var directory. If you open it, you can see our test container cache files. We don't want this file committed, so open our bundle's .gitignore and add var/ to it:

5 lines | object-translation-bundle/.gitignore
// ... line 1
var/
// ... lines 3 - 5

After saving, we can see var/ is now ignored.

If you're wondering why this var directory is here and not somewhere else, it's because when the container cache is created, if not explicitly specified, it climbs up the directory structure starting from our TestKernel, until it finds a composer.json file. It then creates the var directory next to it. You can totally configure this location if you want, to maybe a temp directory but, I like the default location.

Ignore composer.lock

While we're in .gitignore, let's add a couple more things. See our bundle's composer.lock file? Add this to .gitignore as well:

5 lines | object-translation-bundle/.gitignore
// ... lines 1 - 2
composer.lock
// ... lines 4 - 5

It's very important you don't commit this file for bundles. You'll see why soon.

Also add this .phpunit.result.cache file:

5 lines | object-translation-bundle/.gitignore
// ... lines 1 - 3
.phpunit.result.cache

It's generated by PHPUnit, and we don't want to commit it.

Next, let's replace our simple integration test with the real thing: a test that uses our bundle to translate an entity.