Custom environment raises "Class \ does not exist and could not be loaded" error
kniziol opened this issue · 19 comments
While trying to use custom environment an "Class \ does not exist and could not be loaded" error is raised. More details below.
An error
Warning: class_implements(): Class \ does not exist and could not be loaded in /var/www/application/vendor/zalas/phpunit-injector/src/Symfony/Compiler/Discovery/ClassFinder.php on line 25
Stack trace
Call Stack:
0.0009 356336 1. {main}() /var/www/application/vendor/phpunit/phpunit/phpunit:0
0.5061 884072 2. PHPUnit\TextUI\Command::main() /var/www/application/vendor/phpunit/phpunit/phpunit:53
(...)
5.0771 7918968 18. Symfony\Component\DependencyInjection\ContainerBuilder->compile() /var/www/application/vendor/symfony/symfony/src/Symfony/Component/HttpKernel/Kernel.php:643
5.3498 7922728 19. Symfony\Component\DependencyInjection\Compiler\Compiler->compile() /var/www/application/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/ContainerBuilder.php:759
12.5243 18614784 20. Zalas\Injector\PHPUnit\Symfony\Compiler\ExposeServicesForTestsPass->process() /var/www/application/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Compiler/Compiler.php:141
12.5243 18614784 21. Zalas\Injector\PHPUnit\Symfony\Compiler\ExposeServicesForTestsPass->discoverServices() /var/www/application/vendor/zalas/phpunit-injector/src/Symfony/Compiler/ExposeServicesForTestsPass.php:29
12.5243 18614784 22. Zalas\Injector\PHPUnit\Symfony\Compiler\Discovery\PropertyDiscovery->run() /var/www/application/vendor/zalas/phpunit-injector/src/Symfony/Compiler/ExposeServicesForTestsPass.php:41
12.5243 18615160 23. Zalas\Injector\PHPUnit\Symfony\Compiler\Discovery\PropertyDiscovery->findTestCases() /var/www/application/vendor/zalas/phpunit-injector/src/Symfony/Compiler/Discovery/PropertyDiscovery.php:34
12.5243 18615160 24. Zalas\Injector\PHPUnit\Symfony\Compiler\Discovery\ClassFinder->findImplementations() /var/www/application/vendor/zalas/phpunit-injector/src/Symfony/Compiler/Discovery/PropertyDiscovery.php:39
12.5243 18615856 25. Zalas\Injector\PHPUnit\Symfony\Compiler\Discovery\ClassFinder->find() /var/www/application/vendor/zalas/phpunit-injector/src/Symfony/Compiler/Discovery/ClassFinder.php:26
12.6436 18669688 26. Zalas\Injector\PHPUnit\Symfony\Compiler\Discovery\ClassFinder->findClassesInFile() /var/www/application/vendor/zalas/phpunit-injector/src/Symfony/Compiler/Discovery/ClassFinder.php:38
12.6679 18723048 27. Zalas\Injector\PHPUnit\Symfony\Compiler\Discovery\ClassFinder->Zalas\Injector\PHPUnit\Symfony\Compiler\Discovery\{closure}() /var/www/application/vendor/zalas/phpunit-injector/src/Symfony/Compiler/Discovery/ClassFinder.php:68
12.6679 18723048 28. class_implements() /var/www/application/vendor/zalas/phpunit-injector/src/Symfony/Compiler/Discovery/ClassFinder.php:25
A test class
class ButtonServiceWithoutCommonCssClassesTest extends KernelTestCase implements ServiceContainerTestCase
{
use SymfonyContainer;
/**
* Service for buttons
*
* @var ButtonService
* @inject
*/
private $buttonService;
// (...)
/**
* {@inheritdoc}
*/
protected static function createKernel(array $options = []): KernelInterface
{
if (!isset($options['environment'])) {
$options['environment'] = 'test_buttons_common_css_classes'; // <--- Custom environment is passed here
}
return parent::createKernel($options);
}
//
// Trying to set environment using the bootKernel() method fails too
//
// /**
// * {@inheritdoc}
// */
// protected function setUp()
// {
// parent::setUp();
//
// static::bootKernel([
// 'environment' => 'test_buttons_common_css_classes',
// ]);
// }
}
Main question
Are custom environments supported and how to solve this problem?
Looks like a bug. I'm pretty sure I've covered/tested custom environments. I'll have a look when I'm back from holidays (after 18th).
In the meantime, if you have time to debug this issue, I'd suggest to check why the namespace is empty (it's \
). Is there a namespace in your test class?
It's very odd. not trueClassFinder::findImplementations()
is only ever called with ServiceContainerTestCase::class
. Not sure why in your case it's being called with \
. The error message you're getting might be misleading.
Just noticed that your test class extends the KernelTestCase
. Not sure if it causes the issue, but there's no point of doing so as the SymfonyContainer
trait provides all you should need to create the kernel and container.
To be honest I mainly planned to use environment variables (APP_ENV etc) for configuration. It's still possible to do what you were trying to do, but it's a bit awkward imo:
use SymfonyContainer {
createKernel as traitCreateKernel;
}
protected static function createKernel(array $options = []): KernelInterface
{
if (!isset($options['environment'])) {
$options['environment'] = 'footests';
}
return static::traitCreateKernel($options);
}
Perhaps we need to provide a method to override options per test case so you don't have to override the createKernel
method.
@kniziol Would it be possible if you created a small project that reproduces the issue? I'm not experiencing the same behaviour.
I checked again. Trying to extend the KernelTestCase
and including the SymfonyContainer
trait in the same time results in fatal error:
PHP Fatal error: Symfony\Bundle\FrameworkBundle\Test\KernelTestCase and Zalas\Injector\PHPUnit\Symfony\TestCase\SymfonyContainer define the same property ($kernel) in the composition of Tests\AppBundle\FooServiceTest. However, the definition differs and is considered incompatible. Class was composed in tests/AppBundle/FooServiceTest.php
I'd say extending KernelTestCase
is unsupported for now. It's unnecesary and I'm not sure if it's actually needed to support it.
As I said earlier, to override the environment in the way you tried to do is possible with:
namespace Tests\AppBundle;
use AppBundle\FooService;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpKernel\KernelInterface;
use Zalas\Injector\PHPUnit\Symfony\TestCase\SymfonyContainer;
use Zalas\Injector\PHPUnit\TestCase\ServiceContainerTestCase;
class FooServiceTest extends TestCase implements ServiceContainerTestCase
{
use SymfonyContainer {
createKernel as traitCreateKernel;
}
/**
* @var FooService
* @inject
*/
private $foo;
public function testIt()
{
$this->assertInstanceOf(FooService::class, $this->foo);
}
protected static function createKernel(array $options = []): KernelInterface
{
if (!isset($options['environment'])) {
$options['environment'] = 'test_buttons_common_css_classes';
}
return static::traitCreateKernel($options);
}
}
I realise it's a bit verbose. Perhaps it would be nice to expose a method with options that could be overriden in a test case.
I find it easier to define the APP_ENV environment variable either in phpunit.xml, or with zalas/phpunit-globals
:
class FooServiceTest extends TestCase implements ServiceContainerTestCase
{
use SymfonyContainer;
/**
* @var FooService
* @inject
*/
private $foo;
/**
* @env APP_ENV=test_buttons_common_css_classes
*/
public function testIt()
{
$this->assertInstanceOf(FooService::class, $this->foo);
}
}
Anyway, since I can't reproduce the original error I'm going to close this issue. Feel free to report it again with a code reproducer.
I've got a similar situation:
class_implements(): Class Tests\Traits\
does not exist and could not be loaded
I suspect that it's from a test where I am using a shortened namespace path in my initial 'use' blocks: tests/App/Controller/TodoControllerTest.php:9: use Tests\Traits;
,
and then inside the class I will write: use Traits\TestLog;
So, I'm using the namespace as an indication of the type of class being used - I'll do the same in regular code with Vo\Email
or Entity\Profile
for example.
I will try to get a very minimal set of code and repo to duplicate in the next few days.
It has, btw found a number of badly name-spaced files, with the 'Tests' part of the namespace in the wrong place or with a lower-case 't', so it's already done something useful for code cleanliness.
Thanks for reporting back!
What’s the version of phpdocumentor/reflection-docblock that was brought in as a dependency in your case?
I've got the following in my composer.lock, but I'm using the zalas-phpunit-injector-extension.phar via the extensionsDirectory entry.
- phpdocumentor/reflection-docblock is at 4.3.0 and the other related items are
- phpdocumentor/reflection-common 1.0.1
- phpdocumentor/type-resolver 0.4.0
@alister these are the latest.
I haven't tried reproducing your issue with namespace aliases, but it might be environment specific.
I'll try to put a mini-project together to reproduce it over the next couple of days.
Yep, that got it with the class-loading, though with my 3.4 based tests and the ServiceInjectorTest (in namespace Tests\App;
), it didn't get the SerializerInterface.
PHP Fatal error: Uncaught Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException: Service "Symfony\Component\Serializer\SerializerInterface" not found: the container inside "Zalas\Injector\Service\Injector" is a smaller service locator that only knows about the "logger" service. in .../vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/ServiceLocator.php:51
Stack trace:
#0 phar://.../zalas-phpunit-injector-extension.phar/vendor/zalas/injector/src/Service/Injector.php(76): Symfony\Component\DependencyInjection\ServiceLocator->get('Symfony\\Compone...')
#1 phar://.../zalas-phpunit-injector-extension.phar/vendor/zalas/injector/src/Service/Injector.php(61): Zalas\Injector\Service\Injector->getService(Object(Symfony\Component\DependencyInjection\ServiceLocator), Object(Zalas\Injector\Service\Property))
#2 phar://.../zalas-phpunit-injector-extensio in phar://.../zalas-phpunit-injector-extension.phar/vendor/zalas/injector/src/Service/Injector.php on line 78
The Psr-Loggerinterface test in the example did work, and I could also inject one of my own aliased interface that ends up at the correct default object/service until it gets keen on trying to do something with the non-service test - You have requested a non-existent service "Tests\App\BotDetector\BotDetectorNeverBotTest"
. I had aliased BotDetectable
to 'NeverBot - and an earlier quick test injected that in fine.
Do you extend the KernelTestCase? That’s what I haven’t been doing and intially didn’t plan to support it. In retrospect I think we need to support it though.
Another problem on my side is I mainly used this lib with Symfony 4. It needs more testing with Symfony 3.4.
Well, I have figured out about 'SerializerInterface' - I'm not using that anywhere else in my code, so it will have been removed from the container. I put my BotDetectable
interface back in instead, and that's found fine.
As for the test, it's just a extension of PHPUnit\Framework\TestCase
(it just adds the MockeryPHPUnitIntegration trait and a couple of other additional test-quality checks).
So, the test class itself is pretty much:
class ServiceInjectorTest extends MyTestCase implements ServiceContainerTestCase {
use SymfonyContainer;
/**
* @var BotDetectable
* @inject
*/
private $botDetectable;
// and also @inject LoggerInterface
and then a test with a few assertInstanceOf()
to make sure the properties are set as expected. I'll take that as a win. Thanks for the fixes! 👍
@alister for Symfony 3.4 or 4.0 you need to register the ExposeServicesForTestsPass
in order to make private service available in your tests.
There's an issue with importing namespaces from traits. To be fixed upstream: jakzal/injector#3
I'll close this as the original issue is now fixed. If you find any new problems, please report a new issue. Thanks!