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

smb*_*smb 5 php phpunit unit-testing laravel guzzle

我不太确定在这种情况下采用单元测试的方法.没有单元测试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
    }
}

?>
Run Code Online (Sandbox Code Playgroud)

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

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.
}
Run Code Online (Sandbox Code Playgroud)

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

编辑 我的解决方案最终如下:

这个解决方案感觉最正确,而且是Laravel方式.(参见IoC容器)

我会在每个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);
Run Code Online (Sandbox Code Playgroud)

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

public function __construct(GuzzleHttp\Client $client)
{
    $this->client = $client;
}
Run Code Online (Sandbox Code Playgroud)

然后将使用$ this-> client进行所有api调用.

Sim*_*mba 7

对此的"经典TDD"回应是你不应该对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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

现在,您可以只观看您的模拟类,以确保对它的调用与Guzzle API为了拨打电话而预期接收的内容相匹配.

如果你班级的其余部分取决于从Guzzle收到的输出,那么你也可以在你的模拟中定义.