-
-
Save lyrixx/0adb8fd414451596557871d2d9af5695 to your computer and use it in GitHub Desktop.
<?php | |
namespace Tests\Integration; | |
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | |
use Symfony\Component\Config\FileLocator; | |
use Symfony\Component\DependencyInjection\ContainerBuilder; | |
use Symfony\Component\DependencyInjection\Definition; | |
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; | |
class ContainerTest extends KernelTestCase | |
{ | |
private const FILTER_LIST = [ | |
// some services can exist only in dev or prod (thus not in test env) | |
// or some services are behind some features flags | |
// or some services are static (thus they are not real service) | |
]; | |
public function testContainer() | |
{ | |
static::bootKernel(['debug' => true]); | |
$projectDir = static::getContainer()->getParameter('kernel.project_dir'); | |
$container = static::getContainer(); | |
$builder = new ContainerBuilder(); | |
$loader = new XmlFileLoader($builder, new FileLocator()); | |
$loader->load($container->getParameter('debug.container.dump')); | |
$count = 0; | |
foreach ($builder->getDefinitions() as $id => $service) { | |
if ($this->isSkipped($id, $service, $builder, $projectDir)) { | |
continue; | |
} | |
$container->get($id); | |
++$count; | |
} | |
$this->addToAssertionCount($count); | |
} | |
private function isSkipped(string $id, Definition $service, ContainerBuilder $builder, string $projectDir): bool | |
{ | |
if (str_starts_with($id, '.instanceof.') || str_starts_with($id, '.abstract.') || str_starts_with($id, '.errored.')) { | |
return true; // Symfony internal stuff | |
} | |
if ($service->isAbstract()) { | |
return true; // Symfony internal stuff | |
} | |
$class = $service->getClass(); | |
if (!$class) { | |
return true; // kernel, or alias, or abstract | |
} | |
if (\in_array($class, self::FILTER_LIST)) { | |
return true; | |
} | |
$rc = $builder->getReflectionClass($class, false); | |
if (!$rc) { | |
return true; | |
} | |
$filename = $rc->getFileName(); | |
if (!str_starts_with($filename, "{$projectDir}/src")) { | |
return true; // service class not in tests/Integration | |
} | |
if ($rc->isAbstract()) { | |
return true; | |
} | |
return false; | |
} | |
if (!str_starts_with($filename, "{$projectDir}/src")) {
return true; // service class not in tests/Integration
}
Why do we check only classes from src/? This is common that services from vendor packages are configured by the application via a bundle config or custom container extension or compiler pass. We want to check instantiating those as well (to verify we have configured them correctly).
@javaDeveloperKid you're right. I coded that only for my use case. But feel free to adapt it!
Thanks for sharing this.
You can use this to automatically find the XML file:
$xml = file_get_contents($container->getParameter('debug.container.dump'));
Another improvement would be to use the XmlFileLoader so that you don't have to manually parse and work with the XML structure:
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator());
$loader->load(static::getContainer()->getParameter('debug.container.dump'));
foreach ($container->getDefinitions() as $service) {
// $service = Definition now...
}
Very good idea. I'll update the gist asap
Gist updated 🎉 Thanks again
In our project, we have a lot of service locators. I think the test should take these into account and unpack every ServiceLocator that it finds.
Another optimization that can be made: report all failures, instead of only the first one.
Yes! indeed. But it's better to exclude from the DIC theses service (less CPU/time consumed for nothing)
I use this pattern all the time:
About the 2nd problem, I do have lot of private service (almost all of them) and it works nicely.
Symfony, in test env, have a special container where all service are kinda public