Symfony 5 : Mocking private autowired services in Controller functional tests

Why?

Example use case:

Let’s say you are working on a modern Symfony application where frontend and backend are decoupled. Your application is simply a REST API which communicates with fronted by using JSON payloads. Throwing exceptions in your backend and leaving them unhandled is a bad idea. If your backend encounters an exception, you’ll either end up with a crashed application or transfer the responsibility to frontend to handle the exceptions… but it’s not frontend’s job to do that.

So, you’ll need to catch and process all exceptions in your controller action and return an appropriate error response (if an exception happens). That’s easy… just wrap your code in try / catch and process exceptions to produce JSON error responses.

Your controller action is probably using some autowired services. These services can throw two types of exceptions:

  • Exceptions which can be thrown based on user input (contents of the HTTP Request object)
  • Exceptions which can only be thrown if your application is misconfigured and does not depend on user input (for example: missing runtime environment variables)

Testing the response of the first type of exceptions is easy — you can craft a Request in your test case to trigger an exception.

But what about testing exception responses which can not be triggered by building a custom Request object because they don't depend on user input?

Controller sample

Let’s assume that getToken() method of the TokenService is only capable of throwing exceptions which are not based on user input. In order to cover the catch code block with a test, you must force this method to throw an exception. That's why we're going to mock it.

Solution

I’m assuming you already know hot to create a functional test for your controller actions. If you don’t, read the official documentation here

Test class

So, what did we do here?

$client = static::createClient(); and $client->request('GET', '/sample'); are two standard lines of code in almost every functional test in Symfony.
In order to successfully mock an autowired service, we need to create a mock and inject it into the Service Container. That needs to be done after booting the kernel (static::createClient) and before calling the controller action ($client->request()).

In addition to this, you’ll also need to declare TokenService public in your test environment's service container. To do this, open /config/services_test.yaml and make your TokenService public:

All services in Symfony are private by default and unless you have a really good reason, they should stay private on any other environment except test. We need to make the TokenService public for our test, because otherwise we would get an exception when trying to set it ($container->set()) in our test case:

Summary

  • Declare your service as public in test environment's service container (/config/services_test.yaml);
  • Create a functional test client, which boots the kernel and creates a service container (static::createClient());
  • Create a mock of your service;
  • Replace the default definition of your service with the mock dynamically ($container->set());
  • Run the client ($client->request())

Originally published at https://dev.to on February 13, 2021.

Tech enthusiast currently working as a Symfony developer, devops engineer and server administrator. Specialized in e-commerce and advertising domains.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store