How to mock a service in Panther test?

541 Views Asked by At

I have a web page that does an external API call when a form being submitted. My idea is to mock HttpClientInterface as long as a complete E2E scenario will require a running dev server for that purpose, which is not always possible in my case.

Here is what I tried so far:

services.yaml

when@test:
    services:
        _defaults:
            public: true

        test.Symfony\Contracts\HttpClient\HttpClientInterface: '@Symfony\Contracts\HttpClient\HttpClientInterface'

My testing scenario:

class ClientAppointmentControllerTest extends PantherTestCase
{
    public function testForm(): void
    {
        $client = static::createPantherClient();
        
        $response = new MockResponse(null, ['http_code' => 400]);
        $this->mockResponse($response);

        $crawler = $client->request('GET', '/our-survey');

        $form = $crawler->selectButton('Save')->form();
        $client->submit($form);

        static::assertEquals($response->getRequestMethod(), 'POST');
        static::assertEquals($response->getRequestOptions()['body'], []);
        static::assertEquals($response->getRequestUrl(), 'ad');
    }

    private function mockResponse(MockResponse $response): void
    {
        self::getContainer()->set(
            'test.Symfony\Contracts\HttpClient\HttpClientInterface',
            new TraceableHttpClient(new MockHttpClient($response))
        );
    }
}

But it gives me the following error: Symfony\Component\HttpClient\Exception\InvalidArgumentException: MockResponse instances must be issued by MockHttpClient before processing. Apparently it does not work for PantherTestCase (it works for WebTestCase tests though).

Is it possible to mock response somehow during integration testing?

1

There are 1 best solutions below

0
On

Probably this does answer your direct question about your HttpClientInterface mocking like you tried to do, but maybe show another way of approach for testing.

Example we have implementation:

-- config
-- -- services.yaml
-- src
-- -- Controller
-- -- -- ImportantPage.php
-- -- Service
-- -- -- ImportantInformationService.php
class ImportantPage extends AbstractController
{
    #[Route('/important', name: 'important_docs', methods: ['GET'])]
    public function index(Request $request): Response
    {
        $importantInformation = new ImportantInformationService();
        $importantInformation->getDocuments($request->get('category'));
        // <...>
        return $this->render('important_page/index.html.twig', [
            'documents' => $documents,
        ]);
    }
}

class ImportantInformationService
{
    // <...>
    public function getDocuments(string $category): array
    {
        return $this->documentsRepository->findByCategory($category);
    }
}

We want to test ImportantPage controller, but we don't want to test ImportantInformationService service. So we Extend ImportantInformationService service and Mock function response in newMockService.

  1. Let's create test:
class ImportantPageTest extends PantherTestCase
{
    public function testImportantPageNew(): void
    {
        $client = static::createPantherClient();
        $client->request('GET', '/important', ['category' => 'new_documents']);
        $this->assertSelectorTextContains('td.name', 'Document Nr. 123');
    }
}
  1. New configuration for panther: services_panther.yaml
services:
    _defaults:
        public: true

    App\Service\ImportantInformationService:
        public: true
        class: App\Tests\PantherMock\PantherMockImportantInformationService
        # In case you have something in constructor:
        # arguments:
        #     - '@doctrine.orm.entity_manager'
  1. PantherMockImportantInformationService.php
class PantherMockImportantInformationService extends ImportantInformationService
{
    public function getDocuments(string $category): array
    {
        return [
            [
                'name' => 'Document Nr. 123',
                'category' => 'new_documents',
            ],
        ];
    }
}
  1. Make sure panther test configuration is correct in .env.test file:
PANTHER_APP_ENV=panther

File structure should be like this:

-- config
-- -- services.yaml
-- -- services_panther.yaml
-- src
-- -- Controller
-- -- -- ImportantPage.php
-- -- Service
-- -- -- ImportantInformationService.php
-- tests
-- -- E2E
-- -- -- ImportantPageTest.php
-- -- PantherMock
-- -- -- PantherMockImportantInformationService.yaml
-- .env.test

So in general testing could look like this:

// You change request Category parameter and get different response to Mocked service
// $client->request('GET', '/important', ['category' => 'new_documents']);
$client->request('GET', '/important', ['category' => 'old_documents']);

    public function testImportantPageNew(): void
    {
        $client = static::createPantherClient();
        $client->request('GET', '/important', ['category' => 'new_documents']);
        $this->assertSelectorTextContains('td.name', 'Document Nr. 123');
    }

    public function testImportantPageOld(): void
    {
        $client = static::createPantherClient();
        $client->request('GET', '/important', ['category' => 'old_documents']);
        $this->assertSelectorTextContains('td.name', 'Document Nr. 456');
    }
class PantherMockImportantInformationService extends ImportantInformationService
{
    public function getDocuments(string $category): array
    {
        if ($category === 'new_documents') {
            return [
                [
                    'name' => 'Document Nr. 123',
                    'category' => 'new_documents',
                ],
            ];
        }

        if ($category === 'old_documents') {
            return [
                [
                    'name' => 'Document Nr. 456',
                    'category' => 'old_documents',
                ],
            ];
        }

        return []
    }
}

P.S. This is just WORKAROUND what worked for me to some extent. I'm sure there are better ways to do this. But SYMFONY container service mocking (like in WebTestCase) at the time of writing this, was not available in Panther tests, so I found other way to do it.