2012-06-29 46 views
5

我正在使用PhactoryPHPUnit設置PHP Propel項目 的測試套件。我目前正在嘗試對產生外部請求的 函數進行單元測試,並且我想對該請求進行模擬 響應。如何在PHPUnit中模擬外部Web請求?

這裏的類我想測試的一個片段:

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我剛創建的端點對象?

或者我正在做這一切都是錯誤的;有沒有更好的方式讓我單元測試 我的函數依賴於外部Web請求?

我讀過關於「Stubbing and Mocking Web Services」的PHPUnit文檔,但是他們的樣例代碼是40行,不包括必須定義自己的wsdl。我很難相信這是我處理這個問題的最方便的方法,除非SO的優秀人士對此感覺強烈。

非常感謝任何幫助,我一直掛在這一整天。謝謝!!

回答

11

從測試的角度來看,你的代碼有兩個問題:

  1. 網址是硬編碼,讓你改變它用於開發,測試和生產的沒有辦法
  2. 端點知道如何檢索數據。從你的代碼我不能說端點真的做了什麼,但是如果它不是一個低級別的「Just get me Data」對象,它不應該知道如何檢索數據。

用你這樣的代碼,沒有好的方法來測試你的代碼。您可以使用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); 
    } 
} 

如果您需要或想要測試它,你可以創造出生命的測試和行爲中的一個WebService作爲一個「模擬」,總是返回預配置數據。然後你可以調用這個模擬網址而不是真實網址來評估回報。

+2

這就是我最終做的,儘管我並不滿意。一個額外的類只是爲了包裝'file_get_contents' _feels_就像我過度使用。這是一個代碼更改,我只會對它進行測試,而這對我來說很重要。我來自ruby,它有[webmock](https://github.com/bblimke/webmock/)gem,它只是你在最後一段中描述的內容:「創建一個Webservice,它存在於你的測試中,充當「模擬」,總是返回預先配置的數據「。代替我自己編碼,我現在會去模擬對象路線。感謝您的想法! – goggin13

+2

我不認爲這是開銷。想想這樣:你會在很多地方使用這個代碼。一年後,file_get_contents有更好的實現方式,你必須在任何地方改變它。或者認爲你想切換到Buzz(我會推薦)。在上面的代碼中,注入對象並且必須更改一個方法調用。 Mayvbe在你的小例子中是開銷的,但隨着你的應用程序變得越來越大,你可能想重用file_get_contents,你可能會感激它。 – Sgoettschkes