DEV Community

Cover image for Mocking your APIs in a Symfony Project
Élise Duverdier
Élise Duverdier

Posted on

Mocking your APIs in a Symfony Project

If you have a Symfony project which makes HTTP requests to your other projects (whether it is microservices, front and api separated projects, etc), you'll probably need to mock them for your tests.

Here is a simple solution that allows you to create a mock custom class for the test environement in your symfony projects, usable in your unit or functional tests.

This proposed solution is an alternative to using librairies like http-mock which can be harder to setup.


Let's say we have our API Client class with a method getResourceByPath($path), requesting the API endpoint with a library (Guzzle, symfony's HttpClient, cURL, or others) and returning us the json we need:

̀

// a simplified version
namespace AppBundle\Infra\Client;

class ApiClient
{
    public function getResourceByPath(string $path): array
    {
        $response = $this->httpClient->get($path);

        return json_decode($response->getBody());
    }
}

Enter fullscreen mode Exit fullscreen mode

And this class should be declared in your config/services/client.yaml (for Symfony 4+ projects, or app/config.yml for Symfony 3):

services:
  AppBundle\Infra\Client\ApiClient:  # (or app.client.api)
    public: true
    arguments: [...] # any arguments you need
Enter fullscreen mode Exit fullscreen mode

As we don't want to actually perform the request, we'll need to override the method's behaviour.

First, the ApiClient will need an interface, if it doesn't already have one:

namespace AppBundle\Infra\Client;

interface ApiClientInterface
{
    public function getResourceByPath(string $path): array;
}
Enter fullscreen mode Exit fullscreen mode

And edit the ApiClient class with:

class ApiClient implements ApiClientInterface { ... }
Enter fullscreen mode Exit fullscreen mode

Now we can create our mock class, that will perform any logic we need, for example retreive the expected JSON from our local folders instead of the API :

namespace tests\Mock\Client;

class ApiClientMock implements ApiClientInterface
{
    private $mockFolder = dirname(__DIR__) . '/resources/api';

    public function getResourceByPath(string $path): array
    {
        $localFile = sprintf(
          '%s/%s.json',
          $this->mockFolder, 
          $path
        );

        return json_decode(
            file_get_contents($localFile)
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

The structure of our tests folder should look like this:

tests/
  Mock/
    Client/
      ApiClientMock.php
    resources/
      api/
        article.json
        ....
Enter fullscreen mode Exit fullscreen mode

You can now add the mock declaration in your config/services_test.yaml (or app/config_test.yml):

services:
  AppBundle\Infra\Client\ApiClient:    # (or app.client.api)
    class: Tests\Mocks\ApiClientMock
Enter fullscreen mode Exit fullscreen mode

That's all ! When running your test, you should now correctly retreive the content from your local files.


Of course, this example shows a simplified version of the logic, which you can customize to any of your needs. You might want to do more verification on the files and paths called, or create a mapping between them and the local files. 

This system can also be used to mock any other services that can potentially call external services, like a notification service, a Mailer, or anything that can write to a database, to avoid potentially sharing test data to the world :)


There is also an unexpected benefit with this solution: we can use a little hack to use the mock localy -- if the test fails mysteriously and we need to debug ; if we want to check the page render directly ; or if we want to work on a future feature that's not yet available in the API: by switching to the test environment manually (by editing web/app_dev.php or .env), and access the project on the browser, using our content retreived locally.


Image credit: Apis, not related to APIs

Discussion (3)

Collapse
eliseduverdier profile image
Élise Duverdier Author

Thanks :)
You library seems really great, I'll look it up !

Collapse
fcpauldiaz profile image
Pablo Díaz

So glad to see symfony community here!