使用 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 中找不到控制器类