Pubblicato il

Come testare un service privato in Symfony

Autori
Symfony

In Symfony 3.4 hanno reso tutti i services privati per impostazione predefinita, il che significa che non è più possibile richiamare $this->get('my_service_id') nei propri controller per ottenere rapidamente un service.

È stata apportata questa modifica perché l'utilizzo diretto dal container di services non è considerato una buona pratica. Ecco perché i controller consentono l'iniezione dei services con il Type Hinting nei loro metodi e nei loro constructor.

L'unico inconveniente rimasto è che eseguendo i test otteniamo quanto segue

> bin/phpunit
# Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException: The "App\Service\S" service or alias has
# been removed or inlined when the container was compiled. You should either make it public, or stop using the container
# directly and use dependency injection instead.

Problema

Vogliamo creare un service e testarlo prima di integrarlo con il resto del progetto (approccio TDD standard).

Ho un repository R che implementa l'interfaccia RInterface. L'interfaccia RInterface viene utilizzata nel service S (type hinted constructor). Il service S è utilizzato nel controller C (sempre come parametro del constructor).

Quando eseguiamo il test del service S

class STest extends KernelTestCase
{
	public function testGetItems(): void
	{
		self::bootKernel();
		$container = self::$kernel->getContainer();

		$service = $container->get(S::class);
		// ... altro
	}
}

Visualizziamo il seguente messaggio:

> bin/phpunit
# Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException: The "App\Service\S" service or alias has
# been removed or inlined when the container was compiled. You should either make it public, or stop using the container
# directly and use dependency injection instead.

Motivo

Il service può essere integrato quando vengono soddisfatte alcune condizioni.

Per verificare se il service S è presente nel container di test, digitiamo il seguente comando:

> bin/console debug:container 'App\Service\S' --env=test

Di seguito il messaggio che visualizzeremo:

Information for Service "App\Core\Service\S"
=========================================================

 ---------------- ---------------------------------
  Option           Value
 ---------------- ---------------------------------
  Service ID       App\Service\S
  Class            App\Service\S
  Tags             -
  Public           no
  Synthetic        no
  Lazy             no
  Shared           yes
  Abstract         no
  Autowired        yes
  Autoconfigured   yes
 ---------------- ---------------------------------

Come riportato nel messaggio ricevuto eseguendo il test, il service S risulta privato (Public: no). Bisogna tenere presente che, a causa di come funziona il container di Symfony, i services inutilizzati vengono rimossi dal container. Ciò significa che se si dispone di un service privato non utilizzato da nessun altro service, Symfony lo rimuove e non è possibile ottenerlo dal container.

Soluzione

La soluzione è definire esplicitamente il service public in modo che Symfony non lo rimuova. La soluzione più corretta sarebbe quella di creare un alias pubblico solo nell'ambiente di test per il service che si desidera testare.

# config/services_test.yaml
services:
  test_alias.service:s:
    alias: 'App\Service\S'
    public: true

Buon lavoro 👨‍💻