Unit Testing Guzzle inside of Laravel Controller with PHPUnit

谁说胖子不能爱 提交于 2019-12-19 03:15:58

问题


I'm not quite sure which way to approach unit testing in this scenario. None of the examples for unit testing Guzzle quite make sense to me how to implement in this scenario, or perhaps I'm just looking at it incorrectly all together.

The setup: Laravel 4.2 REST API - A controller method is using Guzzle in the method to request data from another api as follows:

<?php

class Widgets extends Controller {
    public function index(){
        // Stuff

        $client = new GuzzleHttp\Client();
        $url = "api.example.com";

        $response = $client->request('POST', $url, ['body' => array(...)]);

        // More stuff
    }
}

?>

I thought that I could do my unit test as follows, and all would just work.

function testGetAllWidgets(){
    $mock_response = array('foo' => 'bar');

    $mock = new MockHandler([
        new Response(200, $mock_response),
    ]);

    $handler = HandlerStack::create($mock);
    $client = new Client(['handler' => $handler]);

    $response = $this->call('GET', '/widgets');

    // Do asserts, etc.
}

However, Guzzle is still making the actual HTTP requests to the external service. My guess was maybe someway of setting the Client creation in the Controller method to use the $handler, but I can't imagine that's the right way to do it. What am I missing?

Edit My solution ended up as follows:

This solution felt the most correct, and the Laravel way. (See IoC Containers)

I would add this above each api call (change the mock responses depending on how many external calls need to be mocked in the api call).

$this->app->bind('MyController', function($app){
    $response_200 = json_encode(array("status" => "successful"));
    $response_300 = json_encode("MULTIPLE_CHOICES");

    $mock = new MockHandler([
        new Response(200, [], $response_200),
        new Response(300, [], $response_300)
    ]);

    $handler = HandlerStack::create($mock);

    return new MyController(new Client(['handler' => $handler]));
});

$params = array();

$response = $this->call('PUT', '/my-route', $params);

And if the controller required the Guzzle client, I added this to the controller:

public function __construct(GuzzleHttp\Client $client)
{
    $this->client = $client;
}

And would then use $this->client for all of the api calls.


回答1:


The "classic TDD" response to this would be that you shouldn't unit test Guzzle at all. Guzzle is a third-party library which should be (and is) tested perfectly adequately by its own developer.

What you need to test is whether your code correctly calls Guzzle, not whether Guzzle works when your code calls it.

The way to do this is as follows:

Rather than doing a new Guzzle() in your controller, you should instead pass a Guzzle object into your controller using dependency injection. Fortunately, Laravel makes this very easy; all you need to do is have a constructor method for your controller class, and have a Guzzle object defined as one of its arguments. Laravel will do the rest of creating the object and passing it in for you. Your constructor can then copy it to a class property so that your other methods can use it.

Your class should now look something like this:

class Widgets extends Controller {
    private $guzzle;
    public function __construct(GuzzleHttp\Client $guzzle)
    {
        $this->guzzle = $guzzle;
    }

    public function index(){
        // Stuff

        $url = "api.example.com";

        $response = $this->guzzle->request('POST', $url, ['body' => array(...)]);

        // More stuff
    }
}

Now your test should be a lot easier to write. You can pass a mock Guzzle object into your class when you test it.

Now you can just watch your mock class to make sure that the calls to it match what the Guzzle API would expect to recieve in order to make the call.

If the rest of your class depends on the output received from Guzzle then you can define that in your mock as well.




回答2:


Use https://github.com/php-vcr/php-vcr package. It helps to record and replay HTTP requests. It is very handy for testing api calls via Guzzle




回答3:


If anyone is struggling with this one then I found replacing:

$this->app->bind('MyController', function($app){

With

$this->app->bind(MyController::class, function($app){

Did the trick for me in Laravel 5.5.44



来源:https://stackoverflow.com/questions/33162281/unit-testing-guzzle-inside-of-laravel-controller-with-phpunit

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!