Skip to content

Commit d6d58de

Browse files
committed
Add support for the test service container from Symfony 4.1
1 parent 1d3d70f commit d6d58de

File tree

6 files changed

+235
-14
lines changed

6 files changed

+235
-14
lines changed

README.md

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,71 @@ The service is found by its type, or an id if it's given in the `@inject` tag.
7676
The `createServiceContainer` method would be usually provided by a base test case or a trait.
7777
In case of Symfony, such a trait is provided by this package (see the next section).
7878

79-
### Symfony
79+
### Symfony Test Container (Symfony >= 4.1)
8080

81-
The simplest way to inject services from a Symfony service container is to include
82-
the `Zalas\Injector\PHPUnit\Symfony\TestCase\SymfonyContainer` trait to get the default
83-
`Zalas\Injector\PHPUnit\TestListener\ServiceContainerTestCase` implementation:
81+
The `Zalas\Injector\PHPUnit\Symfony\TestCase\SymfonyTestContainer` trait provides
82+
access to the test container ([introduced in Symfony 4.1](https://symfony.com/blog/new-in-symfony-4-1-simpler-service-testing)).
83+
Including the trait in a test case implementing the `ServiceContainerTestCase` will make that services are injected
84+
into annotated properties:
85+
86+
```php
87+
use PHPUnit\Framework\TestCase;
88+
use Psr\Log\LoggerInterface;
89+
use Symfony\Component\Serializer\SerializerInterface;
90+
use Zalas\Injector\PHPUnit\Symfony\TestCase\SymfonyTestContainer;
91+
use Zalas\Injector\PHPUnit\TestCase\ServiceContainerTestCase;
92+
93+
class ServiceInjectorTest extends TestCase implements ServiceContainerTestCase
94+
{
95+
use SymfonyTestContainer;
96+
97+
/**
98+
* @var SerializerInterface
99+
* @inject
100+
*/
101+
private $serializer;
102+
103+
/**
104+
* @var LoggerInterface
105+
* @inject logger
106+
*/
107+
private $logger;
108+
109+
public function testThatServicesAreInjected()
110+
{
111+
$this->assertInstanceOf(SerializerInterface::class, $this->serializer, 'The service is injectd by its type');
112+
$this->assertInstanceOf(LoggerInterface::class, $this->logger, 'The service is injected by its id');
113+
}
114+
}
115+
```
116+
117+
Note that `test` needs to be set to `true` in your test environment configuration for the framework bundle:
118+
119+
```yaml
120+
framework:
121+
test: true
122+
```
123+
124+
Even though services are automatically made public by Symfony, the test container makes them available in your tests.
125+
Note that this only happens for private services that are actually used in your app (so are injected into
126+
a public service, i.e. a controller). If a service is not injected anywhere, it's removed by the container compiler.
127+
128+
The kernel used to bootstrap the container is created in a similar way to the `KernelTestCase` known from the FrameworkBundle.
129+
Similar environment variables are supported:
130+
131+
* `KERNEL_CLASS` *required* - kernel class to instantiate to create the service container
132+
* `APP_ENV` default: test - kernel environment
133+
* `APP_DEBUG` default: false - kernel debug flag
134+
135+
These could for example be configured in `phpunit.xml`, or via [global variables](https://github.com/jakzal/phpunit-globals).
136+
137+
### Symfony Container (Symfony 3.4 & 4.0)
138+
139+
The `Zalas\Injector\PHPUnit\Symfony\TestCase\SymfonyContainer` trait gives access to the full Symfony Container
140+
and can be used with any Symfony version.
141+
Opposed to the Test Container approach for Symfony 4.1, this version provides access to each service even if it's
142+
not used by your application anywhere and would normally be removed by the compiler.
143+
This should be treated as a limitation rather than a feature.
84144

85145
```php
86146
use PHPUnit\Framework\TestCase;
@@ -113,8 +173,8 @@ class ServiceInjectorTest extends TestCase implements ServiceContainerTestCase
113173
}
114174
```
115175

116-
To make this work the `Zalas\Injector\PHPUnit\Symfony\Compiler\ExposeServicesForTestsPass` needs to be
117-
registered in test environment:
176+
Since the test container is not available until Symfony 4.1,
177+
you'll also have to register the `Zalas\Injector\PHPUnit\Symfony\Compiler\ExposeServicesForTestsPass` compiler pass:
118178

119179
```php
120180
use Zalas\Injector\PHPUnit\Symfony\Compiler\ExposeServicesForTestsPass;
@@ -134,13 +194,6 @@ class Kernel extends BaseKernel
134194

135195
The compiler pass makes sure that even private services are available to be used in tests.
136196

137-
The kernel is created in a similar way to the `KernelTestCase` known from the FrameworkBundle.
138-
The same environment variables are supported:
139-
140-
* `KERNEL_CLASS` *required* - kernel class to instantiate to create the service container
141-
* `APP_ENV` default: test - kernel environment
142-
* `APP_DEBUG` default: false - kernel debug flag
143-
144197
## Contributing
145198

146199
Please read the [Contributing guide](CONTRIBUTING.md) to learn about contributing to this project.

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"symfony/config": "^3.4 || ^4.0",
1313
"symfony/dependency-injection": "^3.4 || ^4.0",
1414
"symfony/http-kernel": "^3.4 || ^4.0",
15-
"zalas/phpunit-globals": "^1.0"
15+
"zalas/phpunit-globals": "^1.0",
16+
"symfony/framework-bundle": "^3.4||^4.0"
1617
},
1718
"autoload": {
1819
"psr-4": {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Zalas\Injector\PHPUnit\Symfony\TestCase;
5+
6+
use Psr\Container\ContainerInterface;
7+
8+
/**
9+
* Provides a `ServiceContainerTestCase` implementation with the test container from the Symfony FrameworkBundle.
10+
*
11+
* `framework.test` needs to be set to `true` in order for the `test.service_container` to be available.
12+
*/
13+
trait SymfonyTestContainer
14+
{
15+
use SymfonyKernel;
16+
17+
public function createContainer(): ContainerInterface
18+
{
19+
return static::bootKernel()->getContainer()->get('test.service_container');
20+
}
21+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Zalas\Injector\PHPUnit\Tests\Symfony\TestCase\Fixtures\FrameworkBundle;
5+
6+
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
7+
use Symfony\Component\Config\Loader\LoaderInterface;
8+
use Symfony\Component\DependencyInjection\ContainerBuilder;
9+
use Symfony\Component\DependencyInjection\Reference;
10+
use Symfony\Component\HttpKernel\Kernel;
11+
use Zalas\Injector\PHPUnit\Tests\Symfony\TestCase\Fixtures\Service1;
12+
13+
class AnotherTestKernel extends Kernel
14+
{
15+
public function registerBundles()
16+
{
17+
return [
18+
new FrameworkBundle(),
19+
];
20+
}
21+
22+
public function getCacheDir()
23+
{
24+
return \sys_get_temp_dir().'/ZalasPHPUnitInjector/FrameworkBundle/AnotherTestKernel/cache/'.$this->environment;
25+
}
26+
27+
public function getLogDir()
28+
{
29+
return \sys_get_temp_dir().'/ZalasPHPUnitInjector/FrameworkBundle/AnotherTestKernel/logs';
30+
}
31+
32+
public function registerContainerConfiguration(LoaderInterface $loader)
33+
{
34+
$loader->load(function (ContainerBuilder $container) use ($loader) {
35+
$container->register(Service1::class, Service1::class);
36+
37+
$container->register('public_service', \stdClass::class)
38+
->setPublic(true)
39+
->setArguments([
40+
new Reference(Service1::class)
41+
]);
42+
43+
$container->loadFromExtension('framework', ['test' => true]);
44+
$container->setParameter('kernel.secret', '$ecre7');
45+
});
46+
}
47+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Zalas\Injector\PHPUnit\Tests\Symfony\TestCase\Fixtures\FrameworkBundle;
5+
6+
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
7+
use Symfony\Component\Config\Loader\LoaderInterface;
8+
use Symfony\Component\DependencyInjection\ContainerBuilder;
9+
use Symfony\Component\DependencyInjection\Reference;
10+
use Symfony\Component\HttpKernel\Kernel;
11+
use Zalas\Injector\PHPUnit\Tests\Symfony\TestCase\Fixtures\Service1;
12+
use Zalas\Injector\PHPUnit\Tests\Symfony\TestCase\Fixtures\Service2;
13+
14+
class TestKernel extends Kernel
15+
{
16+
public function registerBundles()
17+
{
18+
return [
19+
new FrameworkBundle(),
20+
];
21+
}
22+
23+
public function getCacheDir()
24+
{
25+
return \sys_get_temp_dir().'/ZalasPHPUnitInjector/FrameworkBundle/TestKernel/cache/'.$this->environment;
26+
}
27+
28+
public function getLogDir()
29+
{
30+
return \sys_get_temp_dir().'/ZalasPHPUnitInjector/FrameworkBundle/TestKernel/logs';
31+
}
32+
33+
public function registerContainerConfiguration(LoaderInterface $loader)
34+
{
35+
$loader->load(function (ContainerBuilder $container) use ($loader) {
36+
$container->register(Service1::class, Service1::class);
37+
$container->register('foo.service2', Service2::class);
38+
$container->register('public_service', \stdClass::class)
39+
->setPublic(true)
40+
->setArguments([
41+
new Reference(Service1::class),
42+
new Reference('foo.service2')
43+
]);
44+
45+
$container->loadFromExtension('framework', ['test' => true]);
46+
$container->setParameter('kernel.secret', '$ecre7');
47+
});
48+
}
49+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Zalas\Injector\PHPUnit\Tests\Symfony\TestCase;
5+
6+
use PHPUnit\Framework\TestCase;
7+
use Symfony\Bundle\FrameworkBundle\Test\TestContainer;
8+
use Zalas\Injector\PHPUnit\Symfony\TestCase\SymfonyTestContainer;
9+
use Zalas\Injector\PHPUnit\TestCase\ServiceContainerTestCase;
10+
use Zalas\Injector\PHPUnit\Tests\Symfony\TestCase\Fixtures\Service1;
11+
use Zalas\Injector\PHPUnit\Tests\Symfony\TestCase\Fixtures\Service2;
12+
13+
class SymfonyTestContainerTest extends TestCase implements ServiceContainerTestCase
14+
{
15+
use SymfonyTestContainer;
16+
17+
protected function setUp()
18+
{
19+
if (!\class_exists(TestContainer::class)) {
20+
$this->markTestSkipped('SymfonyTestContainer requires Symfony >= 4.1.');
21+
}
22+
}
23+
24+
/**
25+
* @env KERNEL_CLASS=Zalas\Injector\PHPUnit\Tests\Symfony\TestCase\Fixtures\FrameworkBundle\TestKernel
26+
*/
27+
public function test_it_initializes_the_container_by_booting_the_symfony_kernel()
28+
{
29+
$container = $this->createContainer();
30+
31+
$this->assertInstanceOf(TestContainer::class, $container, 'Full container is not exposed.');
32+
$this->assertTrue($container->has(Service1::class), 'The private service is available in tests.');
33+
$this->assertTrue($container->has('foo.service2'), 'The private service is available in tests.');
34+
$this->assertInstanceOf(Service1::class, $container->get(Service1::class));
35+
$this->assertInstanceOf(Service2::class, $container->get('foo.service2'));
36+
}
37+
38+
/**
39+
* @env KERNEL_CLASS=Zalas\Injector\PHPUnit\Tests\Symfony\TestCase\Fixtures\FrameworkBundle\AnotherTestKernel
40+
*/
41+
public function test_it_ignores_missing_services_when_registering_the_service_locator()
42+
{
43+
$container = $this->createContainer();
44+
45+
$this->assertInstanceOf(TestContainer::class, $container, 'Full container is not exposed.');
46+
$this->assertTrue($container->has(Service1::class), 'The private service is available in tests.');
47+
$this->assertFalse($container->has('foo.service2'), 'The private service is not available in tests.');
48+
$this->assertInstanceOf(Service1::class, $container->get(Service1::class));
49+
}
50+
}

0 commit comments

Comments
 (0)