I have a class with the following function :
public function get(string $uri) : stdClass
{
$this->client = new Client;
$response = $this->client->request(
'GET',
$uri,
$this->headers
);
return json_decode($response->getBody());
}
How can I mock the request method from PHPUnit? I tried different ways but it always tries to connect to the uri specified.
I tried with :
$clientMock = $this->getMockBuilder('GuzzleHttp\Client')
->setMethods('request')
->getMock();
$clientMock->expects($this->once())
->method('request')
->willReturn('{}');
But this didn't work. What can I do? I just need to mock the response to be empty.
Thanks
PD : Client comes from (use GuzzleHttp\Client)
I think as suggested is better to use http://docs.guzzlephp.org/en/stable/testing.html#mock-handler
as it looks like the most elegant way to do it properly.
Thank you all
The mocked Response doesn't need to be anything in particular, your code just expects it to be an object with a getBody method. So you can just use a stdClass, with a getBody method which returns some json_encoded object. Something like:
$jsonObject = json_encode(['foo']);
$uri = 'path/to/foo/bar/';
$mockResponse = $this->getMockBuilder(\stdClass::class)->getMock();
$mockResponse->method('getBody')->willReturn($jsonObject);
$clientMock = $this->getMockBuilder('GuzzleHttp\Client')->getMock();
$clientMock->expects($this->once())
->method('request')
->with(
'GET',
$uri,
$this->anything()
)
->willReturn($mockResponse);
$result = $yourClass->get($uri);
$expected = json_decode($jsonObject);
$this->assertSame($expected, $result);
I prefer this way to mock a Client in PHP. In this example I am using Guzzle Client.
Clone the code or install it via composer
$ composer require doppiogancio/mocked-client
And then...
$builder = new HandlerStackBuilder();
// Add a route with a response via callback
$builder->addRoute(
'GET', '/country/IT', static function (ServerRequestInterface $request): Response {
return new Response(200, [], '{"id":"+39","code":"IT","name":"Italy"}');
}
);
// Add a route with a response in a text file
$builder->addRouteWithFile('GET', '/country/IT/json', __DIR__ . '/fixtures/country.json');
// Add a route with a response in a string
$builder->addRouteWithFile('GET', '{"id":"+39","code":"IT","name":"Italy"}');
// Add a route mocking directly the response
$builder->addRouteWithResponse('GET', '/admin/dashboard', new Response(401));
$client = new Client(['handler' => $builder->build()]);
Once you have mocked the client you can use it like this:
$response = $client->request('GET', '/country/DE/json');
$body = (string) $response->getBody();
$country = json_decode($body, true);
print_r($country);
// will return
Array
(
[id] => +49
[code] => DE
[name] => Germany
)
In addition to the current answer about using MockHandler, it's possible to process the request so that you can validate the calls.
The following example passes a callable which just tests the request method and throws an exception if not POST, if that is OK it returns the response. The principle can be expanded to test other details about the request...
$mock = new MockHandler([
function ($request) {
$this->assertEquals('POST', $request->getMethod());
return new Response(
200,
[],
json_encode([ "access_token" => '1234e' ])
);
},
new Response(
200,
[],
json_encode([ "details" =>
[
[
"orderID" => 229783,
],
[
"orderID" => 416270,
],
],
])
),
]);
$handler = HandlerStack::create($mock);
$client = new Client(['handler' => $handler]);
So the first call to the client has the test included, the second call just returns a response.
Just noticed that any time you use a callable to process the request, you MUST return a Response object if you expect the process to continue.
Related
my question is how to send parameter to my endpoint.
test
public function test_upgrade()
{
$billingChange3 = factory(Plan::class)->create([
'user_id' => 1,
'previous_price' => 150,
'current_price' => 100,
]);
$url = route(
'users.filters'
);
$response = $this->actingAs($this->admin)->getJson($url,['search'=>'upgrade']);
$response->assertStatus(200);
//till here correct
//but here it should return 2 since I have upgrade count 2 as correct it gives error
my api response is wrapped within data, so i have used data below
$response->assertJsonCount(2,'data');
}
my seach keyword can be any thing such as upgrade,downgradeetc. upgrade is whencurrent_price>previous_price``` and I have logic for that in controller
My vue dev tool shows url as below:
first:"https://localhost:800/users/plan?filter=upgrade&page=1"
In test I have passed params as getJson($url,['search'=>'upgrade']
Is that the correct way to pass paramters?
Not correct. See function signature - you pass headers:
public function getJson($uri, array $headers = [])
{
return $this->json('GET', $uri, [], $headers);
}
Correct way:
$response = $this
->actingAs($this->admin)
->getJson(route('users.filters', ['search'=>'upgrade']));
I am trying to record the inbound call but so far I am only getting the 404 call not found error when record is activated.
This is my code so far (I am using Laravel and latest Plivo SDK):
public function __construct(Request $request)
{
$this->authId = config('AUTH_ID');
$this->authToken = config('AUTH_TOKEN');
$this->sourceNumber = config('sms.SMS_SOURCE_NUMBER');
$this->_answerURL = config('voiceCall.CALL_ANSWER_URL');
$this->_recordURL = config('voiceCall.INBOUND_RECORD_URL');
$this->_hangupURL = config('voiceCall.HANGUP_URL');
$this->_plivo = new RestClient($this->authId, $this->authToken);
$this->_response = new Response();
$this->_mp3Url = 'https://s3.amazonaws.com/plivocloud/Trumpet.mp3';
$this->_request = $request;
}
The answer method:
public function answer()
{
$response = $this->_response;
$response->addPlay($this->_mp3Url);
$response->addRecord([
'action' => $this->_recordURL,
'startOnDialAnswer' => "true",
'redirect' => "false",
'maxLength' => 600
]);
Log::useDailyFiles(storage_path().'/logs/debug.log');
Log::info([
'Record' => 'This is from inbound answer',
'Response' => $response
]);
return response($response->toXML(), 200)
->header('Content-Type', 'text/xml');
}
The record method:
public function record()
{
Log::useDailyFiles(storage_path().'/logs/debug.log');
$uuid = $this->_request->input('CallUUID');
Log::info(['This is Call UUID' => $uuid]);
$response = $this->_plivo->calls->startRecording($uuid);
Log::info([
'Record' => 'This is from record inbound record',
'Response' => $response,
'CallUUID' => $this->_request->input('CallUUID'),
'Request' => $this->_request->all(),
]);
}
The request is giving back the proper call uuid, and
$response = $this->_plivo->calls->startRecording($uuid);
is the code which is used in the docs. Does anyone have an idea what am I doing wrong here?
Plivo Sales Engineer here.
Is this an incoming call to your Plivo number? I see that you're returning an XML that contains a Play and Record element. This Record XML will take care of recording the call after the Play ends.
Are you making a Record API request to record this again? Which Call UUID are you passing to this API request?
For an inbound call, the Call UUID is sent to the answer URL. Are you using this Call UUID?
I have an action with annotation route to "showAllLinks"
/**
* #param Request $request
* #return Response
* #Route("/showAllLinks/")
**/
When that action is accessed, I would like to generate URLs (relative and absolute) to several other actions I wrote in the same controller. Is that possible? So far I've tried with several URLs being generated and pushed into array which then would be included in response, but from what I see, Symfony is either asking for
The Response content must be a string or object implementing __toString(), "array" given.
See action below:
public function showAllLinksAction(Request $request)
{
$linksArr = [];
$url1 = $this->generateUrl('helloWorld', [], 302, UrlGeneratorInterface::ABSOLUTE_URL);
$linksArr[] = $url1;
$url2 = $this->generateUrl('goodbye', [], 302, UrlGeneratorInterface::ABSOLUTE_URL);
$linksArr[] = $url2;
$url3 = $this->generateUrl('welcome', [], 302, UrlGeneratorInterface::ABSOLUTE_URL);
$linksArr[] = $url3;
$url4 = $this->generateUrl('welcome', [], 302, UrlGeneratorInterface::ABSOLUTE_URL);
$linksArr[] = $url4;
return new Response($linksArr);
}
You are passing an array to the response object, you should pass an string. These are the params of the response object, from the Symfony documentation:
use Symfony\Component\HttpFoundation\Response;
$response = new Response(
'Content',
Response::HTTP_OK,
array('content-type' => 'text/html')
);
I think you could use
$response = new Response('Content');
or
$response = new Response();
$response->setContent('Content');
In any case, the 'Content' parameter is a string. You are trying to set $linksArr, which is an array. This should work, although may not be the result you want to achieve:
return new Response(implode(",", $linksArr));
I want to get Raw XML Response from this code. But I am getting Object Representation. I like to store the XML Response in a file. I hope there is an workaround.
<?php
//REQUIRED FILES INCLUSION
require_once(__DIR__.'/../../vendor/autoload.php');
require_once(__DIR__.'/../../../Config/Config.php');
//require_once(__DIR__.'/../../../Helper.php');
//NAMESPACE
use \DTS\eBaySDK\Constants;
use \DTS\eBaySDK\Trading\Services;
use \DTS\eBaySDK\Trading\Types;
use \DTS\eBaySDK\Trading\Enums;
//SERVICE CREATION
$Service = new Services\TradingService([
'credentials' => $Config['production']['credentials'],
'sandbox' => false,
'siteId' => Constants\SiteIds::MOTORS,
'httpOptions' => [
'verify' => false
]
]);
//CATEGORY PARAMETERS
$Parameters=array(
//'DetailLevel' => array('ItemReturnCategories'),
'DetailLevel' => array('ReturnAll'),
'WarningLevel' => 'High'
);
//REQUEST
$Request = new Types\GetCategoriesRequestType($Parameters);
$Request->RequesterCredentials = new Types\CustomSecurityHeaderType();
$Request->RequesterCredentials->eBayAuthToken = $Config['production']['authToken'];
$Response = $Service->getCategories($Request);
print_r($Response);
It is possible to pass your own HTTP handler to the SDK via the httpHandler configuration option. This means you can intercept the raw response body before letting the SDK parse it.
The example below shows how to create a simple handler that uses Guzzle to send and process the response. The class is able to save it to a file that you specify. This is better than using the toRequestXml method as that does not give you the actual XML sent by eBay. It gets the object to generate the XML and therefore will be different to the eBay response.
<?php
require __DIR__.'/vendor/autoload.php';
$config = require __DIR__.'/configuration.php';
use \DTS\eBaySDK\Constants;
use \DTS\eBaySDK\Trading\Services;
use \DTS\eBaySDK\Trading\Types;
use \DTS\eBaySDK\Trading\Enums;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
class ResponseLogger
{
private $client;
private $logPath;
public function __construct($logPath)
{
$this->logPath = $logPath;
$this->client = new Client();
}
/**
* This will be called by the SDK and will handle sending the request to the API
* Because of this it will be able to handle saving the response to a file.
*/
public function __invoke(RequestInterface $request, array $options)
{
return $this->client->sendAsync($request)->then(
function (ResponseInterface $response) use ($request) {
$stream = $response->getBody();
file_put_contents($this->logPath, $stream);
/**
* We have to rewind to the start of the steam before giving back to the SDK to process!
* If we don't the SDK will try and parse from the end of the response body.
*/
$stream->rewind();
return $response;
}
);
}
}
$service = new Services\TradingService([
'credentials' => $config['production']['credentials'],
'authToken' => $config['production']['authToken'],
'siteId' => Constants\SiteIds::MOTORS,
'httpHandler' => new ResponseLogger(__DIR__.'/categories.xml')
]);
$response = $service->getCategories(
new Types\GetCategoriesRequestType([
'DetailLevel' => ['ReturnAll'],
'WarningLevel' => 'High'
])
);
if (isset($response->Errors)) {
foreach ($response->Errors as $error) {
printf(
"%s: %s\n%s\n\n",
$error->SeverityCode === Enums\SeverityCodeType::C_ERROR ? 'Error' : 'Warning',
$error->ShortMessage,
$error->LongMessage
);
}
}
I haven't used this package before, but looking at the code on GitHub it looks like \DTS\eBaySDK\Trading\Services\TradingService::getCategories returns an instance of \DTS\eBaySDK\Types\BaseType which contains a method called toRequestXml which you might be able to use.
From GitHub:
/**
* Converts the object to a XML request string.
*
* #return string The XML request string.
*/
public function toRequestXml()
{
return $this->toXml(self::$requestXmlRootElementNames[get_class($this)], true);
}
I want to mock a response to the Guzzle request:
$response = new Response(200, ['X-Foo' => 'Bar']);
//how do I set content of $response to--> "some mocked content"
$client = Mockery::mock('GuzzleHttp\Client');
$client->shouldReceive('get')->once()->andReturn($response);
I noticed I need to add as third parameter the interface:
GuzzleHttp\Stream\StreamInterface
but there are so many implementations of it, and I want to return a simple string. Any ideas?
Edit: now I use this:
$response = new Response(200, [], GuzzleHttp\Stream\Stream::factory('bad xml here'));
but when I check this:
$response->getBody()->getContents()
I get an empty string. Why is this?
Edit 2: this happened to me only when I used xdebug, when it runs normally it works great!
We'll just keep doing this. The previous answer is for Guzzle 5, this is for Guzzle 6:
use GuzzleHttp\Psr7;
$stream = Psr7\stream_for('{"data" : "test"}');
$response = new Response(200, ['Content-Type' => 'application/json'], $stream);
The previous answer is for Guzzle 3. Guzzle 5 uses the following:
<?php
$body = GuzzleHttp\Stream\Stream::factory('some mocked content');
$response = new Response(200, ['X-Foo' => 'Bar'], $body);
Using #tomvo answer and the comment from #Tim - this is what I did for testing Guzzle 6 inside my Laravel app:
use GuzzleHttp\Psr7\Response;
$string = json_encode(['data' => 'test']);
$response = new Response(200, ['Content-Type' => 'application/json'], $string);
$guzzle = Mockery::mock(GuzzleHttp\Client::class);
$guzzle->shouldReceive('get')->once()->andReturn($response);
Guzzle\Http\Message\Response allows you to specify the third parameter as a string.
$body = '<html><body>Hello world!</body></html>';
$response = new Response(200, ['X-Foo' => 'Bar'], $body);
If you'd prefer a solution that implements Guzzle\Stream\StreamInterface, then I recommend using Guzzle\Http\EntityBody for the most straightforward implementation:
$body = Guzzle\Http\EntityBody::fromString('<html><body>Hello world!</body></html>');
$response = new Response(200, ['X-Foo' => 'Bar'], $body);
For Guzzle 7, you can use the GuzzleHttp\Psr7\Utils::streamFor() method as follows:
$data = json_encode(['X-Foo' => 'Bar']);
$stream = Utils::streamFor($data);
And then you can pass the $stream object to the andReturn method of the mocked client.