DEV Community

Cover image for How to test a private service in Symfony
Maico Orazio
Maico Orazio

Posted on

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 👨‍💻

Top comments (5)

Collapse
 
aperanogavin profile image
Gavin • Edited

you litteraly save my night Thank u .

Collapse
 
lyrixx profile image
Grégoire Pineau

Instead of doing that - you can use self::getContainer('private service') in your test class. This is simpler 👍🏼

Collapse
 
mainick profile image
Maico Orazio

@lyrixx Certainly, the solution you indicated is the fastest and simplest.
I tried to indicate in my article the most appropriate solution, which is to create a public alias only in the test environment for the single service you want to test.

Collapse
 
lyrixx profile image
Grégoire Pineau

I didn't understand your article point then :) There is a documentation page for getting private service in test env : symfony.com/doc/current/testing.ht... and this is how everyone should do.

Thread Thread
 
mainick profile image
Maico Orazio

@lyrixx the part of the documentation you mentioned contains this note:

The container from static::getContainer() is actually a special test container. It gives you access to both the public services and the non-removed private services.

The solution I provided in the article is also valid for these cases.