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

Posted

技术标签:

【中文标题】使用 PHPUnit 在 Laravel 控制器中进行单元测试 Guzzle【英文标题】:Unit Testing Guzzle inside of Laravel Controller with PHPUnit 【发布时间】:2016-01-14 17:20:41 【问题描述】:

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

设置: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
    


?>

我认为我可以按如下方式进行单元测试,一切都会正常工作。

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.

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

编辑 我的解决方案如下:

这个解决方案感觉是最正确的,也是 Laravel 的方式。 (See IoC Containers)

我会在每个 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);

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

public function __construct(GuzzleHttp\Client $client)

    $this->client = $client;

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

【问题讨论】:

【参考方案1】:

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

【讨论】:

这是一个有趣的方法。今天晚些时候去研究一下。谢谢!【参考方案2】:

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

您需要测试的是您的代码是否正确调用 Guzzle,而不是 Guzzle 在您的代码调用时是否有效。

方法如下:

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

您的类现在应该如下所示:

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
    

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

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

如果你的类的其余部分依赖于从 Guzzle 收到的输出,那么你也可以在你的模拟中定义它。

【讨论】:

那是正确的,我对测试 Guzzle 一点也不感兴趣,我只是希望 $client->request(...) 调用不进行,只返回一些模拟数据它是成功还是失败 - 取决于我正在进行的单元测试。今天晚些时候要试试这个。谢谢! 这绝对是我认为的首选方法。自从我最终在 laravel 中使用 IoC Container 以来,这感觉是最自然的。我已经用我的最终解决方案更新了我的问题。【参考方案3】:

如果有人为此苦苦挣扎,那么我找到了替换:

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

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

在 Laravel 5.5.44 中帮我解决了这个问题

【讨论】:

以上是关于使用 PHPUnit 在 Laravel 控制器中进行单元测试 Guzzle的主要内容,如果未能解决你的问题,请参考以下文章

在 PHPUnit 中调用路由时如何在 Laravel 8 中模拟 Eloquent 模型

如何在 PHPUnit 中控制台/检查 Laravel 资源的 JSON 响应?

如何使用 laravel 和 phpunit 测试文件上传?

PHPUnit 3.7 在 Laravel 4.1 中找不到控制器类

处理来自 PHPUnit (Laravel 5.2) 的自定义异常

用于 PHPunit 的 Laravel 5.4 模型模拟