Closed rtfjr86 closed 6 years ago
Hello @rtfjr86 . Do you expect different responses for the same client in tests or there is always the same response? If the same then there is easy solution.
@rtfjr86 did you see this repository https://github.com/FriendsOfBehat/ServiceContainerExtension ? Does it help you?
@gregurco Thank for the quick response. I know that the guzzle mock handler can be have multiple responses in a queue. I expect that each of those responses have a different body. I'd like to do something like:
Given the products service returns the following:
| code | body
| 200 | {"products": {"product1": {"name": "test", "status": "enabled"}, "product2": {"name": "test2", "status": "enabled"}}}
| 200 | {"product": {"name": "test", "status": "enabled"}}
I am trying to set theses responses in the guzzle mock handler. It seems to work for the first request, but the second is using the curl handler.
I will definitely take a look at https://github.com/FriendsOfBehat/ServiceContainerExtension. Thanks.
(please forgive my code formatting 😅)
I'm looking to be able to do something like
$handler = new Definition(HandlerStack::class, ['@my_handler']);
on the line below.
@rtfjr86 @gregurco how can define a handle for a client? like this? thanks
$client = new Client([
'base_uri' => "http://example.com",
'handler' => $this->createLoggingHandlerStack([
'{method} {uri} HTTP/{version} {req_body}',
'RESPONSE: {code} - {res_body}',
]),
]);
I want log request body for debug.
@lcp0578 just create listener and listen eight_points_guzzle.post_transaction
event. See: https://github.com/8p/EightPointsGuzzleBundle#listening-to-events
@gregurco thanks
@gregurco
<?php
namespace ApiBundle\EventListener;
use EightPoints\Bundle\GuzzleBundle\Events\GuzzleEventListenerInterface;
class RequestListener implements GuzzleEventListenerInterface
{
private $client;
public function setServiceName($serviceName)
{
dump($serviceName);
$this->client = $serviceName;
}
public function onPostTransaction()
{
// @todo
dump('onPostTransaction');
dump($this->client);
}
}
services.yml
api.request_listener:
class: ApiBundle\EventListener\RequestListener
tags:
- { name: kernel.event_listener, event: eight_points_guzzle.post_transaction, method: onPostTransaction, service: 'eight_points_guzzle.clients.api_department' }
eight_points_guzzle.clients.api_department
is a client service id.
but i don't know,how get eight_points_guzzle.clients.api_department
reuqest info, I want log it.
Can you give me more hints about how to implement RequestListener
?
thx
@lcp0578 try next way:
namespace ApiBundle\EventListener;
use EightPoints\Bundle\GuzzleBundle\Events\GuzzleEventListenerInterface;
use EightPoints\Bundle\GuzzleBundle\Events\PostTransactionEvent;
class RequestListener implements GuzzleEventListenerInterface
{
public function onPostTransaction(PostTransactionEvent $event)
{
if ($event->getServiceName() === 'api_department') {
// do some logic
}
}
}
@gregurco thank you very munch!
Hello, I'd like to readress the initial question of this issue : how can I use the Mock Handler in my EightPointsGuzzleBundle Client class. I have a custom client class (defined as explained in https://github.com/8p/EightPointsGuzzleBundle/blob/master/src/Resources/doc/redefine-client-class.md) but I have a special requirement. I need for my tests to be able to set the MockHandler as the handler to be used for my custom client.
Would it be possible to add an option in the configuration to choose a specific handler to use ?
As far as I could see, there seems to be three possible ways :
no code change to the bundle but requires to use a reference get of the config to change the handler for the stack at test start. (not really DRY)
easiest but least flexible would be to check the kernel.environment variable in the extension class to automatically choose MockHandler upon HandlerStack definition (createHandlerStack) if environment is test
add a "handler" option to the configuration which a set of allowed values (i.e. "default, "mock" and so on) to allow switching handlers depending on environment (it would also allow custom handlers) this requires changes to Configuration class and Extension class
Any feedback on those ideas would be nice to start thinking about its implementation.
@rrajkomar sorry for the delay. Could you please explain why not to mock the whole client and just the HandlerStack? I guess it's better and then you get the full access on call of methods and so on.
I could mock the clients... if I knew how to do it :-) The question from a use case where I'm writing a webservice client api in a symfony bundle and I need to mock api calls to the webservice. I need to either be able to replace the client with a mock in test environment or to be able to tell the actual clients to use a guzzle mock handler.
If you're using SF 4 then just create config file config/services_test.yaml
. This file will be automatically loaded by symfony only in test env. In this file you will have possibility to redefine any services. After that you can create "mock class" in tests/Mocks
folder and redefine client in config/services_test.yaml
:
services:
eight_points_guzzle.client.client_name:
class: App\Tests\Mocks\YourApiClientName
Also you have another possibility to replace service in container in tests env. Just write next functionality in your setUp
method:
protected function setUp(): void
{
parent::setUp();
$client = \Mockery::mock(Client::class);
// define mocked methods
static::$container->set('eight_points_guzzle.client.client_name', $client);
}
I don't like the first solution as it requires me to add some more classes for the mocks (while I won't be needing them) : I'm already using custom classes for the actual clients.
The second solution is better but requires me to write several lines of code to replace each client by a mock...
I was rather expecting a solution like this (which I thought about yesterday) : In test environment I'd define a service for HandlerStack and for MockHandler and then modify the guzzle clients configuration to use the same classes as in dev/prod but with a customized constructor argument : the handler stack.
somewhat like what was suggested in https://github.com/8p/EightPointsGuzzleBundle/issues/195#issuecomment-383716966
That way no need to write multiple lines of code and the configuration remains simple the handler argument for the client could be set to be an externally defined service.
@rrajkomar got it and it makes sense. If you will give me example of your "mock of handler stack" then I can help you with adjustments on bundle's side.
I'll create a fork of the project and make the changes there then link it back here so you can see what I had in mind.
@gregurco : Here you can see what I was thinking of more or less. (sorry didn't have time to write the tests but this should give an idea) : https://github.com/rrajkomar/EightPointsGuzzleBundle/commit/29dee7319ad4c74339bddeac87f64ccce3ba8752
I only configured the mockhandler to be integrated, not sure whether the complete choice (using the options entry) should be given, because the handler can be any callable, but I'm not sure how Definition will react if something else than a class name is provided.
It may be best to only set a boolean node mock_api_calls: T/F instead of allowing a direct setting of the handler class... Also the topic is to simplify the mock of the calls and the MockHandler should be sufficient.
@gregurco : I've been working on the issue again during the week end. I'm close to something that works (with Tests and all) but I hit a wall because of the HandlerStack Definition in the Extension class.
It seems the Definition is created only once and not one per client with is problematic because if you have multiple clients, you cannot define a specific handler for only one client.
Any ideas on how to work past this ? See : https://github.com/rrajkomar/EightPointsGuzzleBundle (Note : there is still some work to be done on it before we can get it useable though)
@rrajkomar yep, you are right. I think there is no restriction to change the definition of handler and you can change that one client will have one separate handler. What do you think?
Mmm Not sure I understood your reply... :confused:
For now all clients have one handler and it creates problems if we want to redefine/mock just one handler for specific client. And I guess in this case we have only one solution: to create separate handler for each client. Write me if it's clear now.
Yes that's, what I started workign on locally but got stuck with the issue that it does not seem to be possible to create multiple definitions with the same "target" class. Or I missed something somewhere.
Here's what I wrote on my dev :
$handlerStackServiceName = sprintf('eight_points_guzzle.handler_stack.%s', $clientName);
$container->setDefinition($handlerStackServiceName, new Definition(HandlerStack::class));
$handler = $container->getDefinition($handlerStackServiceName);
then I tried
if (!$container->hasDefinition('eight_points_guzzle.handler_stack')) {
$container->setDefinition('eight_points_guzzle.handler_stack', new Definition(HandlerStack::class));
}
$handlerStackServiceName = sprintf('eight_points_guzzle.handler_stack.%s', $clientName);
$container->setDefinition($handlerStackServiceName, new ChildDefinition('eight_points_guzzle.handler_stack'));
$handler = $container->getDefinition($handlerStackServiceName);
but neither worked :disappointed:
https://github.com/rrajkomar/EightPointsGuzzleBundle/blob/master/src/DependencyInjection/EightPointsGuzzleExtension.php#L78 - I think here we should be returned Reference
and not Definition
I'll try this evening and get back to you.
Here you go. In the end the issue was not between Definition or Reference but rather that the handler option got overwritten by the foreach on $options
Actually, I've already took the liberty of updating the README.md file to add the related documentation. Unless you want to put a specific page with a more detailed example.
I'm using Behat for testing. My application uses Guzzle to consume other services. When testing, I'm replacing the client in the container with my client that has a mock handler. It works fine until my test makes a second request, then it seems my client is replaced with the original. Is there a way to ensure that a mocked response is always returned when testing? Thanks.
FeatureContext.php
$this ->getSession() ->getDriver() ->getClient() ->getContainer() ->set( 'eight_points_guzzle.client.products_service', $this->getMockClient($responses) );
test.feature
When I go to "/some/url"
And I reload the page
The first request returns the mocked response. The second makes a cURL request.