This course is still being released! Check back later for more chapters.
Integration Testing
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.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeWe 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" />:
| // ... 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:
| // ... 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:
| // ... 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:
| // ... 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.