2

I have the following service which I want to test:

namespace App\Service;

class ApiManager
{
   public function getProjects()
   {
      $projects = $this->pager->fetchAll(
                $this->client->api('projects'),
                'all',
                [['simple' => true]]
      );
   }
}

The service makes use of the Gitlab API bundle for PHP. So the data in $projects looks like this:

[
   0 => [
           'id' => 1,
           'title' => 'Project #1',
           'description' => 'Project description...'
        ],
   1 => [
           'id' => 2,
           'title' => 'Project #2',
           'description' => 'Project description...'
        ],
]

Of course I don't want to test with real data from the API. How can I mock the data that gets returned from the HTTP request in getProjects?

crazynx
  • 132
  • 1
  • 8

4 Answers4

2

Externalize the public function getProjects() into your own service e.g. ProjectService and inject it into your ApiManager.

Create two versions of ProjectService: ProjectService and ProjectMockService, first of course for production, 2nd should return your mocked values (whatever you require here).

Then maintain services.yaml for prod and test environments. Depending on the active environment the correct version of ProjectService will be injected.

LBA
  • 3,859
  • 2
  • 21
  • 60
1

I think you the answer to your question is Dependency Injection.

In simple terms, Dependency Injection is a design pattern that helps avoid hard-coded dependencies for some piece of code or software.

Resources:

What is dependency injection?

Alexander
  • 80
  • 1
  • 10
1

Ideally, your application should translate data coming from say, a third party API into a model contained in your own application. Your application should then work with instances of this model only, rather than the data that came back from the API.

This level of abstraction makes testing easier, as there’s only one place where the API data is used: in some sort of a mapping. A service class could fetch the data from the API and a mapper then converts the response(s) into domain objects.

In response to your actual question, I wouldn’t really test the HTTP call to the API. Testing network calls makes your test suite slow and will fail in an environment without a network connection. Instead, I’d test the mapper with a pre-saved or dummy response from the API. If the API at some point starts returning data in a different “shape”, then you only have one place in your code to change (the mapping layer).

Martin Bean
  • 38,379
  • 25
  • 128
  • 201
0

In this specific case you don't need anything fancy. You already have Pager as a dependency (which I hope you're receiving via __construct()). If that's the case, just go ahead and mock $this->pager->fetchAll() to unit test your method.

Looks like the following:

ApiManagerTest.php

class ApiManagerTest extends TestCase
{
    private $pager;

    private $apiManager;

    public function setUp(): void
    {
        $this->pager = $this->prophesize(Pager::class);

        // Notice we pass the mocked `pager` object here
        $this->apiManager = new ApiManager(
            $this->pager->reveal()
        );
    }

    public function testGetProjects(): void
    {
        // Given
        $projects = $this->givenTwoProjectsExist();

        $this->pager->getProjects(
            'projects',
            'all',
            [['simple' => true]]
        )
        ->shouldBeCalledOnce()
        ->willReturn($projects);

        // When
        $result = $this->apiManager->fetchAll();

        // Then
        self::assertEquals($projects, $result);
    }
}

You can read more about the "Given, When, Then" structure here: https://thephp.website/en/issue/clean-tests-with-php-and-phpunit/

Nawarian
  • 76
  • 1