【问题标题】:symfony: How to mock a response to an Guzzle Api request depending on the requested urlsymfony:如何根据请求的 url 模拟对 Guzzle Api 请求的响应
【发布时间】:2015-11-05 15:45:17
【问题描述】:

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

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

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

测试运行一个调用私有方法的公共方法。这是我班级的私有方法的一部分,其中包含 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”方法,也就是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()

任何人都提示如何实现这一目标?有没有可能?

【问题讨论】:

  • 一种选择可能是将Url的组装和处理分成两个公共方法。但由于公开的唯一原因是可测试性的分裂,所以我很犹豫要不要走这条路。

标签: php unit-testing symfony mocking


【解决方案1】:

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

这就是我们的做法:

三个私有属性被添加到测试类中:

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 方法中。

这样,mock 的行为就像它应该的那样:只有当我的测试类使用匹配值时,它才会返回所需的值。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-10-12
    • 2020-04-22
    • 2021-12-03
    • 2020-03-22
    • 1970-01-01
    • 2013-03-23
    • 2018-01-15
    相关资源
    最近更新 更多