如何在PHPUnit中模拟外部Web请求?

时间:2012-06-29 19:53:55

标签: php unit-testing phpunit

我正在为PHP Propel项目设置测试套件 使用PhactoryPHPUnit。我目前正在尝试进行单元测试 发出外部请求的函数,我想在模拟中存根 响应该请求。

以下是我要测试的课程的片段:

class Endpoint {
  ...
  public function parseThirdPartyResponse() {
    $response = $this->fetchUrl("www.example.com/api.xml");
    // do stuff and return 
    ...
  }

  public function fetchUrl($url) {
    return file_get_contents($url);
  }
  ...

这是我试图写的测试功能。

// my factory, defined in a seperate file
Phactory::define('endpoint', array('identifier'  => 'endpoint_$n');

// a test case in my endpoint_test file
public function testParseThirdPartyResponse() {
  $phEndpoint = Phactory::create('endpoint', $options);
  $endpoint = new EndpointQuery()::create()->findPK($phEndpoint->id);

  $stub = $this->getMock('Endpoint');
  $xml = "...<target>test_target</target>...";  // sample response from third party api

  $stub->expects($this->any())
       ->method('fetchUrl')
       ->will($this->returnValue($xml));

  $result = $endpoint->parseThirdPartyResponse();
  $this->assertEquals('test_target', $result);
}

在我尝试测试代码之后,我现在可以看到,我正在创建一个模拟对象 使用getMock,然后再也不使用它。函数fetchUrl 实际执行,我不想要。但我仍然希望能够使用 Phactory创建了endpoint个对象,因为它有所有正确的字段 从我的工厂定义填充。

我有办法在现有对象上存根方法吗?所以我可以存根 我刚创建的fetch_url端点对象上的$endpoint

或者我这样做是错的;有没有更好的方法让我进行单元测试 我依赖外部网络请求的功能?

我确实阅读了有关“Stubbing and Mocking Web Services”的PHPUnit文档,但他们这样做的示例代码是40行,不包括必须定义自己的wsdl。我很难相信这是我处理这个问题最方便的方式,除非SO的优秀人士强烈反对。

非常感谢任何帮助,我整天都被挂了。谢谢!

1 个答案:

答案 0 :(得分:12)

从测试的角度来看,您的代码有两个问题:

  1. 网址是硬编码的,让您无法改变它以进行开发,测试或制作
  2. Endpoint了解如何检索数据。从你的代码我不能说端点真正做了什么,但如果它不是一个低级别的“Just get me Data”对象,它应该不知道如何检索数据。
  3. 使用这样的代码,没有好的方法来测试代码。您可以使用Reflections,更改代码等。这种方法的问题在于,您不会测试您的实际对象,而是一些反射,这些反射可以通过测试进行更改。

    如果您想编写“好”测试,您的端点应如下所示:

    class Endpoint {
    
        private $dataParser;
        private $endpointUrl;
    
        public function __construct($dataParser, $endpointUrl) {
            $this->dataPartser = $dataParser;
            $this->endpointUrl = $endpointUrl;
        }
    
        public function parseThirdPartyResponse() {
            $response = $this->dataPartser->fetchUrl($this->endpointUrl);
            // ...
        }
    }
    

    现在你可以注入一个DataParser模拟器,它会根据你想要测试的内容返回一些默认响应。

    下一个问题可能是:我如何测试DataParser?大多数情况下,你没有。如果它只是php标准函数的包装器,则不需要。您的DataParser应该非常低级,如下所示:

    class DataParser {
        public function fetchUrl($url) {
            return file_get_contents($url);
        }
    }
    

    如果您需要或想要测试它,您可以创建一个Web服务,它存在于您的测试中并充当“模拟”,始终返回预配置的数据。然后,您可以调用此模拟URL而不是真实的URL并评估返回。