使用 PHPUnit 在 Laravel 控制器内部进行单元测试 Guzzle

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/33162281/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-14 12:30:41  来源:igfitidea点击:

Unit Testing Guzzle inside of Laravel Controller with PHPUnit

phpunit-testinglaravelphpunitguzzle

提问by smb

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.

我不太确定在这种情况下采用哪种方式进行单元测试。单元测试 Guzzle 的所有示例对我来说如何在这种情况下实现都没有意义,或者我可能只是一起错误地看待它。

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

设置:Laravel 4.2 REST API - 控制器方法在方法中使用 Guzzle 从另一个 API 请求数据,如下所示:

<?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?

但是,Guzzle 仍在向外部服务发出实际的 HTTP 请求。我的猜测可能是在 Controller 方法中设置 Client 创建以使用 $handler,但我无法想象这是正确的方法。我错过了什么?

EditMy solution ended up as follows:

编辑我的解决方案如下:

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

这个解决方案感觉最正确,也是 Laravel 的方式。(见 IoC 容器

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).

我会在每个 api 调用上方添加这个(根据 api 调用中需要模拟多少外部调用来更改模拟响应)。

$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:

如果控制器需要 Guzzle 客户端,我将其添加到控制器中:

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

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

然后将 $this->client 用于所有 api 调用。

回答by Simba

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.

对此的“经典 TDD”回应是您根本不应该对 Guzzle 进行单元测试。Guzzle 是一个第三方库,它应该(并且已经)由其自己的开发人员充分测试。

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

您需要测试的是您的代码是否正确调用了 Guzzle,而不是当您的代码调用 Guzzle 时它是否工作。

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.

new Guzzle()您应该使用依赖注入将 Guzzle 对象传递到您的控制器中,而不是在您的控制器中执行 a 。幸运的是,Laravel 使这变得非常容易;您需要做的就是为您的控制器类创建一个构造函数方法,并将一个 Guzzle 对象定义为它的参数之一。Laravel 将完成其余的创建对象并将其传递给您的工作。然后您的构造函数可以将其复制到类属性,以便您的其他方法可以使用它。

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.

现在你的测试应该更容易编写了。您可以在测试时将模拟 Guzzle 对象传递到您的类中。

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.

现在,您只需观察您的模拟类,以确保对其的调用与 Guzzle API 期望接收的调用相匹配。

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

如果您的课程的其余部分取决于从 Guzzle 收到的输出,那么您也可以在模拟中定义它。

回答by Maxim Lanin

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

使用https://github.com/php-vcr/php-vcr包。它有助于记录和重放 HTTP 请求。通过 Guzzle 测试 api 调用非常方便

回答by Tom Metcalfe

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

在 Laravel 5.5.44 中对我有用