Laravel 5.6 Mock Guzzle Response - laravel-5

I am trying to mock a guzzle response from a specific api.
My controller code looks like this (amended for brevity):
class SomeClass
{
private $guzzle;
public function __construct(\GuzzleHttp\Client $guzzle) {
$this->guzzle = new $guzzle();
}
public function makeRequest(){
$client = $this->guzzle;
$url = 'http//somerurl';
$options = [];
$response = $client->request('POST', $url, $options);
return $response;
}
}
And the test looks something like this (again edited)...
public function someTest(){
$mock = $this->createMock(\GuzzleHttp\Client::class);
$mock->method('request')->willReturn([
'response' => 'somedata'
]);
$someClass = new $SomeClass($mock);
$response = $someClass->makeRequest();
$body = $response->getBody();
...
}
At this point the test returns "Call to a member function getBody on null";
How can the getBody response of a guzzle call be tested?
Thank you in advance...

One approach to testing with Guzzle is to configure a MockHandler
http://docs.guzzlephp.org/en/stable/testing.html
So instead of mocking the guzzle client, you create one like so:
public function someTest() {
$mock = new MockHandler([
new Response(200, [], 'The body!'),
// Add more responses for each response you need
]);
$handler = HandlerStack::create($mock);
$client = new Client(['handler' => $handler]);
$someClass = new SomeClass($client);
$response = $someClass->makeRequest();
$body = $response->getBody();
$this->assertSame('The body!', $body);
}

The MockHandler requires you to 'queue' responses, meaning you need to know in what order external API calls will be made. I've taken this a step further and wrapped the MockHandler in another handler capable of stuffing a dummy-response into it at the last moment, if one isn't already waiting in the wings. See https://gist.github.com/kmuenkel/d4d473beb7b2297ac2d8cd480089a738
Just use that trait in your test, and call $this->mockGuzzleResponses(); from the test class's setUp() method. At that point, all requests intended to pass through Guzzle will be available for assertions by way of the $guzzleRequestLog property, and all responses can be mocked by calling $this->guzzleHandler->append(RequestInterface); at the beginning of your test.
Just make sure that all implementations of Guzzle in your code are instantiated by way of the app(Client::class) helper and not new Client. Otherwise the binding override won't take effect. That may have been your issue earlier.

Take a look at my composer package https://packagist.org/packages/doppiogancio/mocked-client.
In my opinion, it's a really simple way to mock a Guzzle Client, binding request URLs with responses.
$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->addRouteWithString('GET', '/country/IT', '{"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 did you will have a fully functional client to use normally
$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
)

Related

How do I console/inspect the JSON response of a Laravel Resource in PHPUnit?

How do I inspect the JSON response in the terminal exactly how it would be returned from the ConversationResource?
Controller
public function show(Conversation $conversation)
{
$conversation->load('participants');
$messages = $conversation
->messages()
->with('sender')
->latest()
->paginate(10);
return new ConversationResource($conversation);
}
Test
/** #test */
public function a_user_can_create_conversation_if_one_doesnt_exist()
{
$this->withoutExceptionHandling();
$this->actingAs($user = User::factory()->create());
$friend = User::factory()->create();
$response = $this->json('GET', '/api/conversation/'. $friend->id)
->assertStatus(201);
dd($response->original);
}
You assign $response to result of assertStatus(). I have no idea what assertStatus returns but likely it does not have json content. I would try to assing to a variable result of this
$response = $this->json('GET', '/api/conversation/'. $friend->id);
and then
dd($response);
Also it would be good to setup xdebug and step trough code, but it is not that easy especially for tests.
If that does not work, then just call same url with https://www.php.net/manual/en/function.file-get-contents.php
and assing to variable and see what it has.

How to use parameter from function to create an URL? Laravel Routing

I'm sending an URL hashed and when i get it i have to show a view on Laravel, so i have those functions on the controller and also some routes:
This are my routes:
Route::post('/sendLink', 'Payment\PaymentController#getPaymentLink');
Route::get('/payment?hash={link}', 'Payment\PaymentController#show');
And this are the functions i have on my controller:
public function getPaymentLink (Request $request){
$budgetId = $request['url.com/payment/payment?hash'];
$link = Crypt::decryptString($budgetId);
Log::debug($link);
//here to the show view i wanna send the link with the id hashed, thats why i dont call show($link)
$view = $this->show($budgetId);
}
public function show($link) {
$config = [
'base_uri' => config('payment.base_uri'), ];
$client = new Client($config);
$banking_entity = $client->get('url')->getBody()->getContents();
$array = json_decode($banking_entity, true);
return view('payment.payment-data')->with('banking_entity', $array);
}
And this is getting a "Page not found" message error.
What i want to to is that when i the client clicks on the link i send him that has this format "url.com/payment/payment?hash=fjadshkfjahsdkfhasdkjha", trigger the getPaymentLink function so i can get de decrypt from that hash and also show him the view .
there is no need to ?hash={link} in get route
it's query params and it will received with $request
like:
$request->hash
// or
$request->get('hash')
You need to define route like this:
Route::get('/payment/{hash}', 'Payment\PaymentController#show');
You can now simply use it in your Controller method like below:
<?php
public function getPaymentLink (Request $request,$hash){
$budgetId = $hash;
// further code goes here
}

How do I mock guzzle request

How do I make a mock of third party api call . That is happening from controller . I have this line of code in controller .
public function store(){
$response = $request->post('http://thirdpaty.app/rmis/api/ebp/requests', [
"headers" => [
'Content-Type' => 'application/json',
],
"json" => [
"data"=>1
]
]);
$data = json_decode($response->getBody()->getContents());
$token = $data->token;
// Saving that token to database
}
And from the test I am doing
$response = $this->post('/to-store-method');
How do I mock the api request . So that in testing I don't have to call the third api request .
Right now I am doing
if(app()->get('env') == 'testing'){
$token = 123;
}else{
//Api call here
}
Is there any better alternative of doing this test
You'll need some way of injecting a mock handler into the Guzzle Client your controller is using. Traditionally, you'd either leverage dependency injection by passing the Guzzle Client via the constructor or through some referenced service in that code that you can mock (using Mockery) behind the scenes.
After that, check out the Guzzle documentation for a peak on how to mock requests in the HTTP Client:
http://docs.guzzlephp.org/en/stable/testing.html
You'd use a MockHandler to do something resembling the following code by building a stack of fake requests and responses.
// Create a mock and queue two responses.
$mock = new MockHandler([
new Response(200, ['X-Foo' => 'Bar'], 'Hello, World'),
new Response(202, ['Content-Length' => 0]),
new RequestException('Error Communicating with Server', new Request('GET', 'test'))
]);
$handlerStack = HandlerStack::create($mock);
$client = new Client(['handler' => $handlerStack]);
// The first request is intercepted with the first response.
$response = $client->request('GET', '/');
Actually, it is a bad practice to mock the network libraries. What I would recommend is to wrap the network request by the httpService and mock the httpService instead to return the required response.
public function store(){
$response = httpService.postData();
$data = json_decode($response->getBody()->getContents());
$token = $data->token;
// Saving that token to database
}
So, the you would get the response as return from the httpService.postData function and you can mock the postData instead of the network library.

problem with multiple HTTP requests in Laravel with Guzzle

I'm using Guzzle version 6.3.3. I want to make multiple HTTP requests from an external API. The code shown below worker perfect for me. This is just one single request.
public function getAllTeams()
{
$client = new Client();
$uri = 'https://api.football-data.org/v2/competitions/2003/teams';
$header = ['headers' => ['X-Auth-Token' => 'MyKey']];
$res = $client->get($uri, $header);
$data = json_decode($res->getBody()->getContents(), true);
return $data['teams'];
}
But now I want to make multiple requests at once. In the documentation of Guzzle I found out how to do it, but it still didn't work properly. This is the code I try to use.
$header = ['headers' => ['X-Auth-Token' => 'MyKey']];
$client = new Client(['debug' => true]);
$res = $client->send(array(
$client->get('https://api.football-data.org/v2/teams/666', $header),
$client->get('https://api.football-data.org/v2/teams/1920', $header),
$client->get('https://api.football-data.org/v2/teams/6806', $header)
));
$data = json_decode($res->getBody()->getContents(), true);
return $data;
I get the error:
Argument 1 passed to GuzzleHttp\Client::send() must implement interface Psr\Http\Message\RequestInterface, array given called in TeamsController.
If I remove the $header after each URI then I get this error:
resulted in a '403 Forbidden' response: {"message": "The resource you are looking for is restricted. Please pass a valid API token and check your subscription fo (truncated...)
I tried several ways to set X-Auth-Token with my API key. But I still get errors and I don't know many other ways with Guzzle to set them.
I hope someone can help me out :)
Guzzle 6 uses a different approach to Guzzle 3, so you should use something like:
use function GuzzleHttp\Promise\all;
$header = ['headers' => ['X-Auth-Token' => 'MyKey']];
$client = new Client(['debug' => true]);
$responses = all([
$client->getAsync('https://api.football-data.org/v2/teams/666', $header),
$client->getAsync('https://api.football-data.org/v2/teams/1920', $header),
$client->getAsync('https://api.football-data.org/v2/teams/6806', $header)
])->wait();
$data = [];
foreach ($responses as $i => $res) {
$data[$i] = json_decode($res->getBody()->getContents(), true);
}
return $data;
Take a look at different questions on the same topic (#1, #2) to see more usage examples.

Laravel: Is it possible to directly test the json response output of a function without actually going through the URI?

From the docs, I can test some json returned from my app using the following:
$response = $this->json('POST', '/user', ['name' => 'Sally']);
$response
->assertStatus(201)
->assertJson([
'created' => true,
]);
However, is it possible to bypass actually calling up the URI with $this->json(*method*, *uri*, *data*); and instead test the direct output of a controller function which returns json? For example, I want to do something like this:
// My controller:
function getPageData(){
$data = array('array', 'of', 'data');
return response()->json($data);
}
// My Test Class:
$controller = new Primary();
$response = $controller->getPageData();
$response->assertJson([
'array', 'of', 'data'
]);
Is this possible?
You could do this for some basic methods, but it might cause side effects:
app(SomeController::class)->someControllerMethod();
Basically, the app() will resolve the dependencies from the constructor, but it won't resolve the method dependencies. So if you typehint something like method(Request $request), it will throw an error.
I'm pretty sure dealing with request() will cause unintentional effects since there's no real request going on.
Edit:
You could then create a TestResponse object, to get all the asserts as well:
$res = app(SomeController::class)->someControllerMethod();
$testRes = new Illuminate\Foundation\Testing\TestResponse($res);
$testRes->assertJson(...); // Will be available

Resources