使用 PHPUnit 在 Laravel 控制器内部进行单元测试
Unit Testing Guzzle inside of Laravel Controller with PHPUnit
我不太确定在这种情况下采用哪种方法进行单元测试。 None 单元测试示例中的 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 调用。
使用https://github.com/php-vcr/php-vcr包。它有助于记录和重放 HTTP 请求。通过 Guzzle
测试 api 调用非常方便
"classic TDD" 对此的回应是您根本不应该对 Guzzle 进行单元测试。 Guzzle 是一个第三方库,应该(并且正在)由其自己的开发人员进行充分的完美测试。
您需要测试的是您的代码是否正确调用了 Guzzle,而不是 Guzzle 在您的代码调用时是否正常工作。
实现方式如下:
与其在控制器中执行 new Guzzle()
,不如使用依赖注入将 Guzzle 对象传递到控制器中。幸运的是,Laravel 使这变得非常容易;您需要做的就是为您的控制器 class 提供一个构造函数方法,并将一个 Guzzle 对象定义为其参数之一。 Laravel 将完成创建对象并将其传递给您的其余部分。然后您的构造函数可以将其复制到 class 属性 以便您的其他方法可以使用它。
您的 class 现在应该看起来像这样:
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 对象传递到您的 class 中。
现在您只需观察您的模拟 class 以确保对它的调用与 Guzzle API 期望接收的内容相匹配,以便进行调用。
如果您的 class 的其余部分取决于从 Guzzle 收到的输出,那么您也可以在模拟中定义它。
如果有人正在为此苦苦挣扎,那么我找到了替代品:
$this->app->bind('MyController', function($app){
与
$this->app->bind(MyController::class, function($app){
在 Laravel 5.5.44
对我有用吗
我不太确定在这种情况下采用哪种方法进行单元测试。 None 单元测试示例中的 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 调用。
使用https://github.com/php-vcr/php-vcr包。它有助于记录和重放 HTTP 请求。通过 Guzzle
测试 api 调用非常方便"classic TDD" 对此的回应是您根本不应该对 Guzzle 进行单元测试。 Guzzle 是一个第三方库,应该(并且正在)由其自己的开发人员进行充分的完美测试。
您需要测试的是您的代码是否正确调用了 Guzzle,而不是 Guzzle 在您的代码调用时是否正常工作。
实现方式如下:
与其在控制器中执行 new Guzzle()
,不如使用依赖注入将 Guzzle 对象传递到控制器中。幸运的是,Laravel 使这变得非常容易;您需要做的就是为您的控制器 class 提供一个构造函数方法,并将一个 Guzzle 对象定义为其参数之一。 Laravel 将完成创建对象并将其传递给您的其余部分。然后您的构造函数可以将其复制到 class 属性 以便您的其他方法可以使用它。
您的 class 现在应该看起来像这样:
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 对象传递到您的 class 中。
现在您只需观察您的模拟 class 以确保对它的调用与 Guzzle API 期望接收的内容相匹配,以便进行调用。
如果您的 class 的其余部分取决于从 Guzzle 收到的输出,那么您也可以在模拟中定义它。
如果有人正在为此苦苦挣扎,那么我找到了替代品:
$this->app->bind('MyController', function($app){
与
$this->app->bind(MyController::class, function($app){
在 Laravel 5.5.44
对我有用吗