symfony:如何根据请求的 url 模拟对 Guzzle Api 请求的响应

symfony: How to mock a response to an Guzzle Api request depending on the requested url

我正在做一个 symfony 2 项目。我有一个 class,它根据输入值创建一个 url 并启动对外部系统的请求。处理响应并返回处理结果。

为此 class 功能测试正在运行。在那种情况下,这意味着我正在 运行 测试,它真正调用外部服务并处理真正的答案。 现在我想添加真正的单元测试,所以我必须模拟我的请求和结果。对于请求,我使用的是 Guzzle Http 客户端。 我想要实现的是,如果我的方法正在调用 url“http://example.com/certain/parameters”,那么我希望得到答案 "baa"。通过这个,我想测试请求 url 是否正确构建以及结果响应是否正确处理。

但是我完全不知道如何进行模拟。

测试运行一个 public 方法,该方法调用一个私有方法。这是我的 class' 私有方法的一部分,其中包含 Guzzle:

/**
 * Fetch the Preview Urls from Cache Server.
 *
 * @param string  $sCacheUrl
 * @param integer $iObjnbr
 *
 * @return mixed
 */
private function fetchPreviewUrls($sCacheUrl, $iObjnbr)
{
    $this->client = $this->client->get($sCacheUrl);
    $response = $this->client->send();
    if ($response->getStatusCode() <> 200) {
        $this->logger->error('Image cache server unreachable. Http status "' . $response->getStatusCode() . '"');
    } else {
        $this->logger->debug('new cache url is "' . $response->getBody(true) . '"');
    }
    $json = $response->getBody(true);
    //echo $json;

    $aPreviews = $this->processJson($json, $iObjnbr);
    //var_dump($aPreviews);
    $aTeaser = array_shift($aPreviews);
    if (empty($aTeaser)) {
        $aTeaser = $aPreviews;
    }
    //var_dump($aTeaser);
    return $aTeaser['url'];
}

棘手的部分是url设置在$client的"get"方法中。然后"send"方法用于获取response,response是send方法返回的对象。 我想将 get 调用的输入值与 send 调用的结果连接起来。

我尝试了很多,但到目前为止没有任何效果。

其中一个无效示例是:

public function testGetPreviewUrlBigImage()
{

    $this->mockLogger = $this->getMock('\Psr\Log\LoggerInterface');
    // given
    $url = 'http://i5.example.com/teaser.php?action=list&objnbr=60963198&size=320';
    $json =
        '{"60963198":{"0":{"url":"http:\/\/i1.example.com\/teaser\/320\/8\/60963198.jpeg","size":320,"type":""}}}';

    $clientMethods = get_class_methods('\Guzzle\Http\Client');
    $this->mockClient = $this->getMock('\Guzzle\Http\Client', $clientMethods);
    $this->mockClient->expects($this->any())->method('get')->method('send')->will(
        $this->returnCallback(
            function ($argument)  use ($json, $url) {
                $response = new \Guzzle\Http\Message\Response(200, [], $json);
                return ($argument == $url) ? $response : false;
            }
        )
    );
    $this->linkService = new DefaultPreviewImageLinkService($this->mockLogger, $this->mockClient);
    // when
    $previewUrl = $this->linkService->getPreviewUrl(60963198, '', DefaultPreviewImageLinkService::IMAGE_BIG);

    // then
    $this->assertStringEndsWith('.jpeg', $previewUrl);
    $this->assertRegExp('/^http:\/\/i[0-9]{1}.*/', $previewUrl);
    $this->assertRegExp('/.*320.*jpeg/', $previewUrl);
}

这会导致致命错误 PHP 致命错误:在 null 上调用成员函数 getPreviewUrl()

有人知道如何实现这一点吗?有可能吗?

francisco-spaeth 的帮助下,我解决了这个问题:

我们是这样做的:

测试中添加了三个私有属性class:

private $mockLogger;
private $mockClient;
private $linkService;

我添加了一个准备模拟的方法

public function prepareMocks($url, $json)
{
    $responseInterface = $this->getMockBuilder('\Guzzle\Http\Message\Response')
                              ->disableOriginalConstructor()
                              ->getMock();
    $responseInterface->method('getBody')
                      ->with($this->equalTo(true))
                      ->will($this->returnValue($json));

    $requestInterface = $this->getMock('\Guzzle\Http\Message\RequestInterface');
    $requestInterface->method('send')->will($this->returnValue($responseInterface));

    $this->mockClient = $this->getMock('\Guzzle\Http\Client');

    $this->mockClient->method('get')
                     ->with($this->equalTo($url))
                     ->will($this->returnValue($requestInterface));
    $this->linkService = new DefaultPreviewImageLinkService($this->mockLogger, $this->mockClient);
}

在测试方法中调用如下:

public function testGetPreviewUrlBigImage()
{
    // Given:
    $url = 'http://i1.example.com/teaser.php?action=list&objnbr=60963198&size=320';
    $json = '{"60963198":{"0":{"url":"http:\/\/i1.example.com\/teaser\/320\/8\/60963198.jpeg",'
        . '"size":320,"type":""}}}';
    $this->prepareMocks($url, $json);

    $class_methods = get_class_methods($this->linkService);
    // When:
    $previewUrl = $this->linkService->getPreviewUrl(60963198, '', DefaultPreviewImageLinkService::IMAGE_BIG);
    // Then:
    $this->assert.....
}

通过在一个方法中准备模拟,我保持代码干净,因为每次测试都必须调用它。 我只是将所需的输入值输入 prepareMock 方法。

通过这个模拟的行为就像它应该的那样:如果匹配值被我测试的class使用,它只会返回所需的值class。