Mock Slim endpoint POST requests with PHPUnit - php

I want to test the endpoints of my Slim application with PHPUnit. I'm struggling to mock POST requests, as the request body is always empty.
I've tried the approach as described here: Slim Framework endpoint unit testing. (adding the environment variable slim-input)
I've tried writing to php://input directly, but I've found out php://input is read only (the hard way)
The emulation of the environment works correctly as for example the REQUEST_URI is always as expected. I've found out that the body of the request is read out in Slim\Http\RequestBody from php://input.
Notes:
I want to avoid calling the controller methods directly, so I can test everything, including endpoints.
I want to avoid guzzle because it sends an actual request. I do not want to have a server running while testing the application.
my test code so far:
//inherits from Slim/App
$this->app = new SyncApiApp();
// write json to //temp, does not work
$tmp_handle = fopen('php://temp', 'w+');
fwrite($tmp_handle, $json);
rewind($tmp_handle);
fclose($tmp_handle);
//override environment
$this->app->container["environment"] =
Environment::mock(
[
'REQUEST_METHOD' => 'POST',
'REQUEST_URI' => '/1.0/' . $relativeLink,
'slim.input' => $json,
'SERVER_NAME' => 'localhost',
'CONTENT_TYPE' => 'application/json;charset=utf8'
]
);
//run the application
$response = $this->app->run();
//result: the correct endpoint is reached, but $request->getBody() is empty
Whole project (be aware that I've simplified the code on stackoverflow):
https://github.com/famoser/SyncApi/blob/master/Famoser.SyncApi.Webpage/tests/Famoser/SyncApi/Tests/
Note 2:
I've asked at the slimframework forum, link:
http://discourse.slimframework.com/t/mock-slim-endpoint-post-requests-with-phpunit/973. I'll keep both stackoverflow and discourse.slimframework up to date what is happening.
Note 3:
There is a currently open pull request of mine for this feature: https://github.com/slimphp/Slim/pull/2086

There was help over at http://discourse.slimframework.com/t/mock-slim-endpoint-post-requests-with-phpunit/973/7, the solution was to create the Request from scratch, and write to the request body.
//setup environment vals to create request
$env = Environment::mock();
$uri = Uri::createFromString('/1.0/' . $relativeLink);
$headers = Headers::createFromEnvironment($env);
$cookies = [];
$serverParams = $env->all();
$body = new RequestBody();
$uploadedFiles = UploadedFile::createFromEnvironment($env);
$request = new Request('POST', $uri, $headers, $cookies, $serverParams, $body, $uploadedFiles);
//write request data
$request->write(json_encode([ 'key' => 'val' ]));
$request->getBody()->rewind();
//set method & content type
$request = $request->withHeader('Content-Type', 'application/json');
$request = $request->withMethod('POST');
//execute request
$app = new App();
$resOut = $app($request, new Response());
$resOut->getBody()->rewind();
$this->assertEquals('full response text', $resOut->getBody()->getContents());
The original blog post which helped to answer was at http://glenneggleton.com/page/slim-unit-testing

Related

Guzzle PSR7 request with multipart/form-data

I created a simple API in Lumen (application A) which:
receives PSR-7 request interface
replaces URI of the request to the application B
and sends the request through Guzzle.
public function apiMethod(ServerRequestInterface $psrRequest)
{
$url = $this->getUri();
$psrRequest = $psrRequest->withUri($url);
$response = $this->httpClient->send($psrRequest);
return response($response->getBody(), $response->getStatusCode(), $response->getHeaders());
}
The above code passes data to the application B for the query params, x-www-form-urlencoded, or JSON content type. However, it fails to pass the multipart/form-data. (The file is available in the application A: $psrRequest->getUploadedFiles()).
Edit 1
I tried replacing the Guzzle invocation with the Buzz
$psr18Client = new Browser(new Curl(new Psr17Factory()), new Psr17Factory());
$response = $psr18Client->sendRequest($psrRequest);
but still, it does not make a difference.
Edit 2
Instances of ServerRequestInterface represent a request on the server-side. Guzzle and Buzz are using an instance of RequestInterface to send data. The RequestInterface is missing abstraction over uploaded files. So files should be added manually http://docs.guzzlephp.org/en/stable/request-options.html#multipart
$options = [];
/** #var UploadedFileInterface $uploadedFile */
foreach ($psrRequest->getUploadedFiles() as $uploadedFile) {
$options['multipart'][] = [
'name' => 'file',
'fileName' => $uploadedFile->getClientFilename(),
'contents' => $uploadedFile->getStream()->getContents()
];
}
$response = $this->httpClient->send($psrRequest, $options);
But still no luck with that.
What I am missing? How to change the request so files will be sent properly?
It seems that $options['multipart'] is taken into account when using post method from guzzle. So changing the code to the $response = $this->httpClient->post($psrRequest->getUri(), $options); solves the problem.
Also, it is important not to attach 'content-type header.

Send Body as raw using Guzzle

I am trying to use Guzzle to send POST request to my web service. this service accepts body as raw. It works fine when I use postman but I doesn't using Guzzle. when using Guzzle, I get only the webservice description as I put the web service URL in the browser.
here is my code:
$body = "CA::Read:PackageItems (CustomerId='xxxxxx',AllPackages=TRUE);";
$headers = [
....
....
];
$client = new Client();
$response = $client->request('POST', 'http://172.19.34.67:9882/TisService',$headers,$body);
echo $body = $response->getBody();
seems headers or body doesn't pass through.
Try like this
$response = $client->request('POST', 'http://172.19.34.67:9882/TisService',['headers' => $headers, 'body' => $body]);
I have recently had to implement Guzzle for the first time and it is a fairly simple library to use.
First I created a new Client
// Passed in our options with just our base_uri in
$client = new Client(["base_uri" => "http://example.com"]);
I then created a POST request, not how I am using new Request instead of $client->request(... though. This doesn't really matter to much that I've used new Request though.
// Create a simple request object of type 'POST' with our remaining URI
// our headers and the body of our request.
$request = new Request('POST', '/api/v1/user/', $this->_headers, $this->body);
so in essence it would look like:
$request = new Request('POST', '/api/v1/user/', ['Content-Type' => "application/json, 'Accept' => "application/json"], '{"username": "myuser"}');
$this->headers is a simple key-value pair array of our request headers making sure to set the Content-Type header and $this->body is a simple string object, in my case it forms a JSON body.
I can simply then just call the $client->send(... method to send the request like:
// send would return us our ResponseInterface object as long as an exception wasn't thrown.
$rawResponse = $client->send($request, $this->_options);
$this->_options is a simple key-value pair array again simple to the headers array but this includes things like timeout for the request.
For me I have created a simple Factory object called HttpClient that constructs the whole Guzzle request for me this is why I just create a new Request object instead of calling $client->request(... which will also send the request.
What you essentially need to do to send data as raw is to json_encode an array of your $data and send it in the request body.
$request = new Request(
'POST',
$url,
['Content-Type' => 'application/json', 'Accept' => 'application/json'],
\GuzzleHttp\json_encode($data)
);
$response = $client->send($request);
$content = $response->getBody()->getContents();
Using guzzle Request GuzzleHttp\Psr7\Request; and Client GuzzleHttp\Client

How to add form data on Post requests for Buzz HTTP Client on Laravel?

I'm using Buzz HTTP Client for Laravel.
I have a problem adding form data to my POST requests, since it wasn't specified in it's wiki/documentation.
Listed below are the two ways of sending requests.
Example 1:
$response = Buzz::post('http://api.website.com/login');
//how do I add a "username", and "password" field in my POST request?
echo $response;
echo $response->getContent;
Example 2:
$request = new Buzz\Message\Request('POST', '/', 'http://google.com');
$response = new Buzz\Message\Response();
//how do I add a "username", and "password" field in my POST request?
$client = new Buzz\Client\FileGetContents();
$client->send($request, $response);
echo $request;
echo $response;
The answer here is going to really depend on what the API expects. Lets assume, the API expects the password and username sent as JSON in the content of the request. The example http request would look something like:
POST /login HTTP/1.1
Content-Type: application/json
{
"username": "bugsBunny",
"password": "wh4tsUpD0c"
}
To do this with Buzz, this should work:
$jsonPayload = json_encode([
‘username’ => ‘bugsBunny’,
‘password’ => ‘wh4tsUpD0c
]);
$headers = ['Content-Type', 'application/json'];
$response = Buzz::post('http://api.website.com/login', $headers, $jsonPayload);
If you're attempting to submit a form on a given website, you shouldn't use the above method. Instead use Buzz's built in form method which will attach the correct headers.
use Buzz\Message\Form;
$request = new Form(Form::METHOD_POST, ‘login’, ‘api.website.com’);
$request->setFields([
‘username’ => ‘bugsBunny’,
‘password’ => ‘wh4tsUpD0c’
]);
$response = new Buzz\Message\Response();
$client = new Buzz\Client\Curl();
$client->send($request, $response);
On a side note, I'd suggest not using this library. The library is, as you stated, Laravel integration for Buzz. The issue here is, the author should have made buzz a dependency listed in composer, rather than include the Buzz source directly. This prevents updates to Buzz from making their way into this project. You can see on the actual Buzz repo, the last commit was 29 days ago. Also if another package is using Buzz and including it correctly by composer, composer would install both packages. But when an instance of Buzz was created, you couldn't be certain which version was being loaded. You should just use Buzz, which can be found on packagist.
// assuming $headers and $jsonPayload are the same as in previous example.
$browser = new Buzz\Browser();
$response = $browser->post('http://api.website.com/login', $headers, $jsonPayload);
It was foolish of me to not read the code first before asking.
The form data is actually pased on the third parameter for the function. Though it accepts strings only so don't forget to json encode your data.
Buzz Class
public function post($url, $headers = array(), $content = '')
{
....
....
}
Buzz::post($url, array(), json_encode(array('Username'=>'usernamexx','Password'=>'p#$$w0rD')) );

Send raw json HTTP request for an API call in Yii 1.x.x

I asked a similar question earlier, in a nutshell I have an API application that takes json requests and outputs an json response.
For instance here is one of the requests that I need to test out, how can I use this json object with my testing to emulate a 'real request'
{
"request" : {
"model" : {
"code" : "PR92DK1Z"
}
}
The response is straightforward (this bit has been done).
From other users on here this is the optimised method using Yii to do this, I am just unsure how to emulate the json request - e.g essentially send a JSON HTTP request, can anyone assist on how to do this?
public function actionMyRequest() {
// somehow add my json request...
$requestBody = Yii::app()->request->getRawBody();
$parsedRequest = CJSON::decode($requestBody);
$code = $parsedRequest["request"]["model"]["code"];
}
I don't understand if you want your app to send an http request and get the result or at the opposite receive a http request
I answered for the first assumption, I'll change my answer if you want the other
For me the best way to send an HTTP request is to use Guzzle http client.
This is not a yii extension, but you can use third party libraries with yii.
Here's an example from Guzzle page:
$client = new GuzzleHttp\Client();
$res = $client->get('https://api.github.com/user', [
'auth' => ['user', 'pass']
]);
echo $res->getStatusCode(); // 200
echo $res->getHeader('content-type'); // 'application/json; charset=utf8'
echo $res->getBody();
So in your case you could do something like:
public function actionMyRequest() {
$client = new GuzzleHttp\Client();
$res = $client->get('https://api.your-url.com/');
$requestBody = $res->getBody();
$parsedRequest = CJSON::decode($requestBody);
$code = $parsedRequest["request"]["model"]["code"];
}

ZF2 - Cookie exception header when posting to remote url

I am trying to post a form via cURL from inside a zend framework 2 console route.
I am doing something like
php public/index.php myroute
To call the route from terminal. Inside that route is the following:
$request = new \Zend\Http\Request;
$request->setMethod('POST');
$request->setUri('http://somesite.com');
$request->getHeaders()->addHeaders([
'Content-Type' => 'application/x-www-form-urlencoded; charset=UTF-8'
]);
/* set post fields */
$aData = array('hello' => 'world');
$request->setContent($request->getPost()->toString());
/* just to look at request string in console */
echo $request->toString()."\n";
/* post the data */
$client = new \Zend\Http\Client;
$client->setAdapter("Zend\Http\Client\Adapter\Curl");
$response = $client->dispatch($request);
Problem is that this throws a cookie exception when ran from console:
POST http://somesite.com HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
hello=world
======================================================================
The application has thrown an exception!
======================================================================
Zend\Http\Header\Exception\InvalidArgumentException
Cookie name cannot contain these characters: =,; \t\r\n\013\014 (WEBUK=)
If anyone can point out how to get around this I would be very grateful.
The code works in a normal route OKAY...it only complains about the cookie issue when I run it from console.
Fixed my issue for now. I have decided to give up on using zf2 methods for this and instead just use Guzzle.
$aData = array('hello' => 'world');
$httpClient = new GuzzleHttp();
$r = $httpClient->post('http://somesite.com', [
'body' => $aData
]);
$response = $httpClient->send($r);

Categories