Closed davidyell closed 1 year ago
Seems that if you pass query params you don't get a response. If you do not include the query params when mocking the path, it works fine.
Whaddya know!
I don't understand the problem you're talking about
Given my example code here
<?php
use donatj\MockWebServer\Response;
require 'vendor/autoload.php';
$server = new \donatj\MockWebServer\MockWebServer;
$server->start();
$endpoint = $server->setResponseOfPath('/path', new Response('Hello World', [], 200));
echo file_get_contents($endpoint . '?getParam=chicken&getParam=egg');
I get back my defined 'Hello World' regardless of the defined GET parameters, which is expected?
I will try and explain with a cut down example of code, but it is essentially a Guzzle request to another service I'd like to mock.
The end to end test looks like this.
public function testWithAService500Response()
{
$responseData = [
'errors' => [
'source' => ['type' => 'unhandled'],
'language_key' => 'Rude alert! Rude alert! An electrical fire has knocked out my voice recognition unicycle! Many Wurlitzers are missing from my database.',
'detail' => [
'info' => 'ParseError',
'line' => '9001',
'file' => '/var/www/AnExampleFileWithAnError.php'
]
]
];
$stubServiceResponse = new StubResponse( // This is the package's response class
body: \Safe\json_encode($responseData),
headers: ['Content-Type' => 'application/vnd.api+json'],
status: 500
);
$filter = ['filter' => ['occasion_id' => '6e6c85d7-20d5-485a-80bd-7796ff8d2898']];
// Copy pasta from the instance in the parent test class `setUp()`
$this->mockServer->setResponseOfPath(
sprintf('/api/v1/claims/total-approved?%s', http_build_query($filters)),
$response
);
// This sends a request to the application, a full end to end request/response
$this->get(sprintf('/v1/claims/total-approved?%s', http_build_query($filter)), 400);
}
NB The urls are similar! http://localhost/v1/claims/total-approved
vs http://localhost:5100/api/v1/claims/total-approved
the port 5100 is the remote to be mocked.
Then in the application code, it has to send a request to another service, which is using Guzzle. As you can see it just proxies through what it gets from the test.
$response = $this->get('/api/v1/claims/total-approved', [RequestOptions::QUERY => ['filter' => ['occasion_id' => $filter->occasionId]]]);
The assertion of the response from the application, which should be a 400 in this case, as I wrap the failed remote service call 500 :) Bit confusing I know.
So my expectation is that when the application sends a request to the remote service, that it should return the configured Response object. However I always get back a 200 OK
and I have no clue why that might be.
I wondered if it was the query params, but I was wrong.
The main difference in this use-case to those in the README file is that they call the mocked service directly, whereas this is expecting the code to call localhost:5100
but instead of the Docker container responding (it's off during test) the mock webserver would instead reply with the configured response.
I am really struggling, in the application code, to catch the response 500 and have the code manage it.
I appreciate it seems a complex example, and I hope I've explained it š The package was suggested by a colleague, so it's new to me just this morning š
Am I reading right that you're mocking one path yet calling a path you didn't mock?
mocked: /api/v1/claims/total-approved?%s
not mocked:: /v1/claims/total-approved?%s
I don't know what you'd expect to happen, as the path you're calling isn't defined - calling an unmocked path by default returns 200 and the content of the request as a JSON body, which is probably what you're seeing since you didn't mock the path you're calling.
You can see an example of that by running
<?php
require 'vendor/autoload.php';
$server = new \donatj\MockWebServer\MockWebServer;
$server->start();
print_r(
file_get_contents($server->getServerRoot() . '/unmocked-path')
);
You can disable that default behaviour by changing the default response ala
<?php
use donatj\MockWebServer\Responses\NotFoundResponse;
require 'vendor/autoload.php';
$server = new \donatj\MockWebServer\MockWebServer;
$server->start();
$server->setDefaultResponse(new NotFoundResponse);
print_r(
file_get_contents($server->getServerRoot() . '/unmocked-path')
);
Also, you shouldn't be including the GET parameters as part of the "path" at all, per the spec of URI path does not include the query
Am I reading right that you're mocking one path yet calling a path you didn't mock?
Exactly that, yes, the unit of code under test calls the mocked code.
So I am hoping to plumb the mock in by simply matching it's host and port to the one the code under test calls.
Also, you shouldn't be including the GET parameters as part of the "path" at all
Yes, that was what I realised and closed the issue as I felt it might have been the route cause of my issue. However I do call the same path in the 3 tests in this test-case file. So hopefully the response instance can be replaced each time?
So in summary is this not an appropriate library for my use-case? I did look at https://wiremock.org/ before this library was suggested by my colleague š
I've been over this thread a number of times trying to get my head around what you're trying to do - this is what keeps throwing me here:
// This sends a request to the application, a full end to end request/response
$this->get(sprintf('/v1/claims/total-approved?%s', http_build_query($filter)), 400);
What is this doing exactly? Is it triggering some sort of API connector to make a GET request, or is it triggering a controller in some sort of application?
The comment leads me to believe the latter?
The end to end test sends a request to an endpoint built in a Symfony application.
This triggers a controller, service class, etc. Part of that feature is that it calls another api to get some data. Which it then returns as it's api endpoint response. Hence acting as a proxy.
When the test of this code runs, that remote api doesn't exist. It's just a host and port. So that needs to be mocked.
So when the Symfony class sends it's Guzzle request out to the api endpoint, instead of hitting the remote api, it gets a pre-defined response mock from the Mock Web Server, which is defined in the test.
So the code under test just calls localhost:5100
and when the remote service is up and running in prod for example, it'll return it's data, but in the test pipeline where that service doesn't exist. I was hoping that his package could respond to that request, by configuring the mock web server to respond to localhost:5100
with a fixed mock response instead, to simulate the missing remote api.
It's an odd implementation for sure!
OK! So that clears it up greatly - thank you!
The similarity in the naming just really threw me for a loop š
So then /v1/claims/total-approved
is the path to your controller, and /api/v1/claims/total-approved
is the api endpoint you are attempting to mock. I think I'm with you now, lol. Sorry.
So here's a little working example of what I think you're looking to do:
<?php
use donatj\MockWebServer\Response;
require 'vendor/autoload.php';
$server = new \donatj\MockWebServer\MockWebServer(5100);
$server->start();
$responseData = [
'errors' => [
'source' => [ 'type' => 'unhandled' ],
'language_key' => 'Rude alert! Rude alert! An electrical fire has knocked out my voice recognition unicycle! Many Wurlitzers are missing from my database.',
'detail' => [
'info' => 'ParseError',
'line' => '9001',
'file' => '/var/www/AnExampleFileWithAnError.php',
],
],
];
$endpoint = $server->setResponseOfPath(
'/v1/claims/total-approved', new Response(json_encode($responseData))
);
your_controller();
$request = $server->getLastRequest();
echo 'raw body: ' . $request->getInput() . PHP_EOL . PHP_EOL;
echo 'parsed body: ' . var_export($request->getParsedInput(), true) . PHP_EOL;
// below is an API request representing your controller/api connector for this example
function your_controller() : void {
$postdata = http_build_query(
[
'param1' => 'some content',
'param2' => 'doh',
]
);
$opts = [
'http' =>
[
'method' => 'POST',
'header' => 'Content-Type: application/x-www-form-urlencoded',
'content' => $postdata,
],
];
$context = stream_context_create($opts);
$result = file_get_contents('http://127.0.0.1:5100/v1/claims/total-approved', false, $context);
}
raw body: param1=some+content¶m2=doh
parsed body: array (
'param1' => 'some content',
'param2' => 'doh',
)
This is almost exactly it. The odd thing is that once the Symfony application code makes a request using Guzzle to the mocked endpoint, no further code in that function is executed.
I can dd()
in the top of the method and get an output, but if I add dd()
after the call to guzzle $this->guzzle->get($microserviceUrl)
I get no output.
I think this is the core of my issue, as the function needs to react to the microservice response status code.
function fetchDataWithGuzzle() {
$response = $guzzle->get('/api/v1/claims/total-approved');
dd($response); // No output if the above endpoint is mocked
if ($response->getStatusCode() !== 200) {
throw new MicroserviceException('Something fell over', $response->getStatusCode());
}
return $response;
}
It feels like perhaps the mock server is hooking the call, and then not returning back to the calling context to continue execution. If I comment out the the setResponseOfPath()
in the test, I get output from dd()
š
I have had some time to work through this issue with my colleague and we have discovered a bug in our application code. We have an exceptions listener which is catching the mocked response exception and handling it.
This means that my feature code to handle the response, never gets called.
Thanks for the help, but I will close this issue now as we've proved this a userland issue š
I have been asked to create an end to end test which covers an endpoint. This endpoint is a proxy for another web service.
I am trying to use this package to mock the remote web service responses, so that I can create a test for the various http codes the service it calls can return.
For some reason, when I set a response for a path, and then call my services endpoint I do not get any body back to assert against.
My test
setUp
creates an instance of the server, running on the same port as my remote service,localhost:5100
so I was expecting, perhaps incorrectly, that when my e2e test called the endpoint url, the code there would try and get a response from the remote service and instead hit the mock web server onlocalhost:5100
instead, and then return an instance ofdonatj\MockWebServer\Response
instead ofGuzzleHttp\Psr7\Response
as that is what I configured on my$this->mockServer->setResponseOfPath()
.I am really struggling to understand why I can build a stub response, set it to the path, but when my code under test calls the url, the returned response is a Guzzle one with no body.