PHPUnit: Mocking external data provider in functional tests
Mocking is a common thing in the world of automated testing, nothing to write home about. Nevertheless, I decided to document how to do it in Symfony with PHPUnit for future generations, or for when I forget and am too lazy to read the documentation (whichever comes first).
So I have an API application, it accepts REST requests and returns JSON responses, but some of the resources act as proxy to other external providers - how to test those?
Here is an example controller I have:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <?php class MyController extends AbstractController { protected $externalDataLoader; public function __construct(ExternalDataLoader $externalDataLoader) { $this->externalDataLoader = $externalDataLoader; } /** * @Route ("my/resource/{id}") */ public function myAction(int $id): Response { $data = $this->externalDataLoader->load('external/path', $id); // ... data is processed and formatted here. $result = $data; return $this->createResponse($result); } } |
and this ExternalDataLoader
is registered as my_external_data_provider
in
the DI container.
So a simple action that takes an argument, makes a request to external resource and returns processed data. Now how to test this?
When it comes to REST APIs, I like to write functional (end-to-end) tests for two reasons:
- It’s not a tragedy when some piece of code is not covered with tests
- The actual functionality, as used from the outside, is tested
So here is a simple example on how to mock such an external data provider when writing a functional test:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | <?php public function myActionTest(): void { $loaderMock = $this->getServiceMockBuilder('my_external_data_provider') ->setMethods(['load']) ->getMock(); $loaderMock->expects($this->once()) ->method('load') ->with('external/path', 1) ->willReturn(['something', 'something else']); if ($this->client === null) { $this->client = static::createClient(static::getKernelOptions()); } $this->client->getContainer()->set('my_external_data_provider', $loaderMock); $this->client->request('GET', '/my/resource/1'); $responseData = json_decode($this->client->getResponse()->getContent(), true); $this->assertSame([ 'data' => [ 'something', 'something else', ], 'jsonapi' => [ 'version' => '1.0', ] ], $responseData); } |
The first part mocks the externalDataLoader
(my_external_data_provider
service)
and its load
method with parameters and return value, and replaces it
in the DI container.
The second part makes the
actual request to /my/resource/1
, and this is where the magic happens -
mocked externalDataLoader
is actually used, the parameters are checked and mocked
return value is returned by the load
method.
And that’s it, simple mocking.
Happy testing!