CakePHP 3 - 在集成测试中模拟 HTTP\Client()

CakePHP 3 - Mock HTTP\Client() in integration test

我正在使用 Cake\Http\Client (book) 访问一些外部应用程序。在我的单元测试中,我实际上无法使用这些服务。

那么如何在控制器集成测试中模拟Http Client呢?如果我可以规定确切的响应,而无需它发出实际的 HTTP 请求,那就太好了。

在我的例子中,请求是在控制器操作中发出的,目标是同一服务器上的其他应用程序。但是,我觉得这个问题的解决方案应该独立于 HTTP 目标。

考虑到我在文档或 api 页面中找不到任何内容,我很确定没有内置解决方案来模拟 Http\Client 对象。

所以我使用了另一个 PHP HTTP 客户端 Guzzle。 Guzzle 确实允许通过排队预定义的响应来进行模拟,这正是我想要的。

但是,与 CakePHP 的顺利集成并非易事,因为 Guzzle 客户端构造函数需要传递模拟堆栈。我通过创建客户端工厂解决了这个问题:

<?php

namespace App\Guzzle;

use GuzzleHttp\Client;

/**
 * Guzzle factory
 * 
 * Created to make guzzle clients mockable
 */
class GuzzleFactory
{
    static $options = [];

    /**
     * Create new client
     * 
     * @return \GuzzleHttp\Client
     */
    static function create()
    {
        return new Client(self::$options);
    }

    /**
     * Set handler stack in global options for new clients
     * 
     * @param \GuzzleHttp\HandlerStack $handlerStack
     */
    static function setTest($handlerStack)
    {
        self::$options['handler'] = $handlerStack;
    }

    /**
     * Set options array to default
     */
    static function reset()
    {
        self::$options = [];
    }
}

然后在我的控制器测试中我有以下功能:

/**
 * Set Guzzle mock responses
 * 
 * @param array $list
 */
public function setGuzzleResponses($list)
{
    if (!is_array($list))
    {
        $list = [$list];
    }
    $mock = new MockHandler($list);
    $handlerStack = HandlerStack::create($mock);
    GuzzleFactory::setTest($handlerStack);
}

这样在测试中我可以调用:

    // ...
    $this->setGuzzleResponses([
        new Response(200, [], json_encode($response))
    ]);
    $this->get("/page/index"); // This request will make an HTTP call
    // ...

另见 http://docs.guzzlephp.org/en/stable/testing.html