EightPointsGuzzleBundle icon indicating copy to clipboard operation
EightPointsGuzzleBundle copied to clipboard

Guide "How to mock clients in tests"

Open gregurco opened this issue 6 years ago • 11 comments

Write a guide how to mock clients in functional tests.

gregurco avatar Jan 04 '18 11:01 gregurco

@gregurco : Here is how I did it: https://gist.github.com/Neirda24/d662dda169518dc75a5beb924c4182c2 tell me what you think

Neirda24 avatar Jan 15 '18 16:01 Neirda24

Any updates on this topic. This is definitely a prerquisite for this bundle to be considered usable in a production system.

rrajkomar avatar Sep 28 '18 17:09 rrajkomar

I plan to write it as soon as will finish with guide for plugins. It takes some time.

gregurco avatar Sep 29 '18 20:09 gregurco

Any thoughts about how to properly provide mock clients in functional tests?

I've been able to configure the bundle to use the MockHandler as described, but don't know how to properly provide the mocked responses in my tests -- the MockHandler queue is always empty.

bropp avatar Aug 13 '19 18:08 bropp

Any news about it?

As we use this as our one of the main bundle need some documentation (examples is great too) about how to mock it the right way.

ggagosh avatar Aug 14 '19 20:08 ggagosh

@bropp @ggagosh there are a lot of methods how to mock client. Each method is better in special cases. Could you please provide more details on your requirements from your projects and I will try to provide more information. Probably based on these answers we will be able to create documentation page.

gregurco avatar Aug 16 '19 20:08 gregurco

Hi, first of all, thank you for your lib. I'd like to know if there is a example of how to do the mock. I replaced the handler, it's the MockHandler, but I can't add any request to the queue in the tests. I tried the setHandler method from Guzzle Client and I tried to use reflection class to get the Mock class and append things, but without success.

Could somebody help me with this, please? Thnx in advance

matheusfaustino avatar Nov 25 '19 01:11 matheusfaustino

Well, I think I found a way through. One thing that I didn't mention before it was that I'm using api-platform and because I'm using api-platform, I'm using its ApiTestCase to write the functional tests, and the problem was that my modifications weren't being applied in the container. But, if I disable the kernel reboot (static::createClient()->disableReboot()), it keeps the modification through all the request at that particular test.

So, I used the code from the tests that @rrajkomar wrote for #221 and disabling the reboot:

static::createClient()->disableReboot();

$cli = static::createClient()->getContainer()->get('eight_points_guzzle.client.<client_name>');
$handlerStack = $cli->getConfig('handler');
$handlerStackRefl = new \ReflectionClass($handlerStack);
$handler = $handlerStackRefl->getProperty('handler');
$handler->setAccessible(true);
$mock = $handler->getValue($handlerStack);

After that, you can append any request into the MockHandler, like:

$mock->append(new Response(200, [], ''));

Api plataform uses SF4, so I just set the MockHandler in the services_test.yaml:

# config/services_test.yaml
eight_points_guzzle:
    clients:
        <client_name>:
            handler: 'GuzzleHttp\Handler\MockHandler'

All that set, you're good to go. I hope it helps ^^

matheusfaustino avatar Nov 25 '19 03:11 matheusfaustino

Coming back to this topic after a while, and a lot more use case experience, I'm no longer convinced the MockHandler was been the best way to implement the mocking mechanism. Now using a lot of Behat tests, I'm faced with an issue that could not be solved by any of the proposed solution (I even tried an adapted version of @matheusfaustino 's solution, sadly it didn't work as expected)

When using Behat testing, the MockHandler that you fill up with MockResponses is not taken into account once the actual code is called. This is where disabling the client reboot might have solved some use cases when using the symfony driver (or so I hoped) but even then, when switching to any other driver the solution would no longer be working.

All this leads me to believe going towards a middleware based mocking mechanism might be the best way to handle this problem (although I'm definitely not a fan of a record / replay system, this is so far the most usable I found)

Also it might be the opportunity to revisit the discussion we had way back about setting up a base interface for middlewares (#232): With autowiring now being a best practice, we could use the interface to get an easily extendable system for middlewares. (The same kind of system could also be used for plugins, currently having to go change the Kernel class is rather cumbersome)

@gregurco WDYT ?

rrajkomar avatar Mar 30 '21 16:03 rrajkomar

I am just a couple of years late, I solved these problems building a Guzzle client that uses a router to dispatch mocked routes/responses.

https://packagist.org/packages/doppiogancio/mocked-client

@rrajkomar let me know if my package can help you.

doppiogancio avatar Jun 21 '23 17:06 doppiogancio

The solution of @matheusfaustino worked flawless, thnx. As of today, the getConfig() option is deprecated and will be removed in Guzzle 8.0 (https://github.com/guzzle/guzzle/issues/2514). In case your static analyser complains about deprecated function usage this should be the alternative:

$handler = self::getContainer()->get('eight_points_guzzle.handler.{client_name}');
$handler->append(new Response(...));

Btw 1: Removed reflection usage because a service definition is exposed when a handler is set inside the config. Btw 2: I've also removed the disableReboot() statement, as it's unnecessary when the test only contains a single request. The kernel only reboots during multiple requests. Remember to call disableReboot() if you use self::$client->request() multiple times.

SherinBloemendaal avatar Jan 19 '24 11:01 SherinBloemendaal