How to test a private service in Symfony

mainick

Maico Orazio

Posted on October 4, 2023

How to test a private service in Symfony

In Symfony 3.4, they made all services private by default, which means you can no longer call $this->get('my_service_id') in your controllers to quickly obtain a service.

This change was made because direct usage of services from the container is considered a bad practice. That's why controllers allow services to be injected using Type Hinting in their methods and constructors.

The only remaining inconvenience is that when running tests, we get the following:

> 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.
Enter fullscreen mode Exit fullscreen mode

Problem

We want to create a service and test it before integrating it with the rest of the project (standard TDD approach).

I have a repository R that implements the RInterface interface. The RInterface interface is used in the S service (type hinted constructor). The S service is used in the C controller (again, as a constructor parameter).

When we run the test for the S service...

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

        $service = $container->get(S::class);
        // ... other
    }
}
Enter fullscreen mode Exit fullscreen mode

We see the following message:

> 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.
Enter fullscreen mode Exit fullscreen mode

Reason

The service can be integrated when certain conditions are met.

To check if the S service is present in the test container, we type the following command:

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

Here is the message we will see:

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
 ---------------- ---------------------------------
Enter fullscreen mode Exit fullscreen mode

As mentioned in the received message when running the test, the S service appears as private (Public: no). It's important to note that due to how the Symfony container works, unused services are removed from the container. This means that if you have a private service not used by any other service, Symfony removes it, and you cannot retrieve it from the container.

Solution

The solution is to explicitly define the S service as public so that Symfony does not remove it. The most appropriate solution would be to create a public alias only in the test environment for the service you want to test.

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

Good work 👨‍💻

💖 💪 🙅 🚩
mainick
Maico Orazio

Posted on October 4, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related