Replacing private services with mocks during tests

Changing your container on the fly.

Recently i wanted to create an E2E test for a project i was working on. However, for some of its data, the test would talk to some remote API, to fetch data. Which i didn’t want to happen in the test.

So i wrote the following code, and assumed everything would work:

$client = self::createClient();
$repo = new MemoryUserRepository();
self::$container->set(RemoteUserRepository::class, $repo);
$client->request(
  'POST',
  '/users/create',
  [],
  [],
  ['CONTENT_TYPE' => 'application/json'],
  json_encode(
    // user data
  )
);

However, i was quickly greeted with the following error. I had tried to replace a private service.

There was 1 error:

1) App\Tests\E2E\CreateUserTest::testCanCreateUsers
Symfony\Component\DependencyInjection\Exception\InvalidArgumentException: The "App\Domain\Repository\RemoteUserRepository" service is private, you cannot replace it.

The … service is private, you cannot replace it.

A quick google told me that i had 3 options, make all my services public, which has its own issues, or to mark this specific service as public, or to add a custom version of the RemoteUserRepository as a service for my test environment.

The last option would’ve been fine, but i wanted to replace it with different mocks, depending on what i needed for that test. Perhaps i wanted to create a test where i made sure that the remote repository was never called.

Making this service public wouldn’t have been that bad. But it felt weird to change my production container, just for my tests. Thankfully, you can create a services.yaml, just for you tests: services_test.yaml. Another upside of that is that it is clear these changes are just for your tests.

So i ended up with the following services_test.yaml:

#config/services_test.yaml
services:
  App\Domain\Repository\RemoteUserRepository:
    public: true

Avatar
Gert de Pagter
Software Engineer

My interests include software development, math and magic.

Related