Je migre notre projet vers Symfony 4. Dans mes suites de tests, nous avons utilisé PHPUnit pour des tests fonctionnels (je veux dire, nous appelons des points de terminaison et nous vérifions le résultat). Souvent, nous nous moquons des services pour vérifier les différentes étapes.
Depuis que j'ai migré vers Symfony 4, je suis confronté à ce problème: Symfony\Component\DependencyInjection\Exception\InvalidArgumentException: The "my.service" service is already initialized, you cannot replace it.
Lorsque nous le redéfinissons comme ceci: static::$container->set("my.service", $mock);
Uniquement pour les tests, comment puis-je résoudre ce problème?
Je vous remercie
Enfin, j'ai trouvé une solution. Peut-être pas le meilleur, mais ça marche:
J'ai créé une autre classe de conteneur de test et je remplace la propriété services
à l'aide de Reflection:
<?php
namespace My\Bundle\Test;
use Symfony\Bundle\FrameworkBundle\Test\TestContainer as BaseTestContainer;
class TestContainer extends BaseTestContainer
{
private $publicContainer;
public function set($id, $service)
{
$r = new \ReflectionObject($this->publicContainer);
$p = $r->getProperty('services');
$p->setAccessible(true);
$services = $p->getValue($this->publicContainer);
$services[$id] = $service;
$p->setValue($this->publicContainer, $services);
}
public function setPublicContainer($container)
{
$this->publicContainer = $container;
}
Kernel.php:
<?php
namespace App;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
class Kernel extends BaseKernel
{
use MicroKernelTrait;
public function getOriginalContainer()
{
if(!$this->container) {
parent::boot();
}
/** @var Container $container */
return $this->container;
}
public function getContainer()
{
if ($this->environment == 'prod') {
return parent::getContainer();
}
/** @var Container $container */
$container = $this->getOriginalContainer();
$testContainer = $container->get('my.test.service_container');
$testContainer->setPublicContainer($container);
return $testContainer;
}
C'est vraiment moche, mais ça marche.
Le remplacement est obsolète depuis Symfony 3.3. Au lieu de remplacer le service, vous devriez essayer d'utiliser des alias. http://symfony.com/doc/current/service_container/alias_private.html
Vous pouvez également essayer cette approche:
$this->container->getDefinition('user.user_service')->setSynthetic(true);
avant de faire $container->set()
J'ai quelques tests comme celui-ci (le vrai code effectue certaines actions et renvoie un résultat, la version de test renvoie simplement false pour chaque réponse).
Si vous créez et utilisez une configuration personnalisée pour chaque environnement (par exemple: un services_test.yaml, ou dans Symfony4 probablement tests/services.yaml), et que vous incluez d'abord dev/services.yaml, mais remplacez ensuite le service souhaité, le la dernière définition sera utilisée.
app/config/services_test.yml:
imports:
- { resource: services.yml }
App\BotDetector\BotDetectable: '@App\BotDetector\BotDetectorNeverBot'
# in the top-level 'live/prod' config this would be
# App\BotDetector\BotDetectable: '@App\BotDetector\BotDetector'
Ici, j'utilise une interface comme nom de service, mais cela fera de même avec le style '@ service.name'.