Last active
February 6, 2025 08:01
-
-
Save lyrixx/0adb8fd414451596557871d2d9af5695 to your computer and use it in GitHub Desktop.
Test applications services can boot
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} | |
} |
hey @lyrixx
thanks for this snippet.
I'm trying to implement this in one huge project, and I'm facing some memory limit problems. I tried to run with 'debug' => false
, or to shutdown kernel, but still have a huge memory usage with this test. Do you have some hints about this? thanks!
On project I'm working ATM:
- 6557 services in total
-
so 1008 services testedOK (1 test, 1008 assertions) Time: 00:01.455, Memory: 119.00 MB
So it looks like it does not consume too much memory
Maybe you have a memory leak somewhere...
we have more than 20.000 services and 4000 services tested, memory consumption ends at ~600 Mo just for this test 😞
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
FYI, I ended up with something like this as a PoC:
The differences from gist's version:
vendor/symfony
, instead of narrowing tosrc
only (our app has several dirs with production code, I did not want to list them all or to prepare complex logic)false
Anyway, thanks for sharing, it was good to make some kind of experiment. What I found more than
lint:container
is that we rely onEventDispatcher
, not onEventDispatcherInterface
, so when traceable event dispatcher is injected intest
environment, there'sTypeError
, which means we should fix the signatures and use the interface as a contract.PS. in our case
bootstrapContainer()
has logic, I just removed it as it's crafted for our code. But if there are any things that need to be pre-defined before services are created (like$_SERVER['HTTP_CLIENT_IP']
for example), you also need to do it before iterating over container.