2011-04-05 18 views
20

如何在PHPUnit中測試由受測試的構造函數調用的方法?例如下面的簡單代碼將不起作用,因爲當我聲明存根方法時,存根對象已經被創建並且我的方法被調用,未被標記。保存由類的構造函數調用的方法

類測試:

class ClassA { 
    private $dog; 
    private $formatted; 

    public function __construct($param1) { 
    $this->dog = $param1;  
    $this->getResultFromRemoteServer(); 
    } 

    // Would normally be private, made public for stubbing 
    public getResultFromRemoteServer() { 
    $this->formatted = file_get_contents('http://whatever.com/index.php?'.$this->dog); 
    } 

    public getFormatted() { 
    return ("The dog is a ".$this->formatted); 
    } 
} 

測試代碼:

class ClassATest extends PHPUnit_Framework_TestCase { 
    public function testPoodle() { 
    $stub = $this->getMockBuilder('ClassA') 
       ->setMethods(array('getResultFromRemoteServer')) 
       ->setConstructorArgs(array('dog52')) 
       ->getMock(); 

    $stub->expects($this->any()) 
     ->method('getResultFromRemoteServer') 
     ->will($this->returnValue('Poodle')); 

    $expected = 'This dog is a Poodle'; 
    $actual = $stub->getFormatted(); 
    $this->assertEquals($expected, $actual); 
    } 
} 

回答

14

問題不在於方法的殘缺,而在於你的班級。

您正在構造函數中工作。爲了將對象設置爲狀態,您需要獲取遠程文件。但是這一步不是必須的,因爲對象不需要該數據處於有效狀態。在您實際撥打getFormatted之前,您不需要文件中的結果。

你可以推遲加載:

class ClassA { 
    private $dog; 
    private $formatted; 

    public function __construct($param1) { 
    $this->dog = $param1;  
    } 
    protected getResultFromRemoteServer() { 
    if (!$this->formatted) { 
     $this->formatted = file_get_contents(
      'http://whatever.com/index.php?' . $this->dog 
     ); 
    } 
    return $this->formatted; 
    } 
    public getFormatted() { 
    return ("The dog is a " . $this->getResultFromRemoteServer()); 
    } 
} 

所以你懶加載的時候實際上是需要它來遠程訪問。現在你根本不需要存根getResultFromRemoteServer,但可以改爲存根getFormatted。您也不需要打開您的API進行測試,然後公開發表getResultFromRemoteServer

在一個旁註,即使這只是一個例子,我想重寫類讀取

class DogFinder 
{ 
    protected $lookupUri; 
    protected $cache = array(); 
    public function __construct($lookupUri) 
    { 
     $this->lookupUri = $lookupUri; 
    } 
    protected function findById($dog) 
    { 
     if (!isset($this->cache[$dog])) { 
      $this->cache[$dog] = file_get_contents(
       urlencode($this->lookupUri . $dog) 
      ); 
     } 
     return $this->cache[$id]; 
    } 
    public function getFormatted($dog, $format = 'This is a %s') 
    { 
     return sprintf($format, $this->findById($dog)); 
    } 
} 

因爲它是一個Finder中,它可能會更有意義,其實有findById公衆現在。只是保持它的保護,因爲這是你的例子。


另一個選項將延長主題被測,並與自己的實現返回Poodle替代方法getResultFromRemoteServer。這意味着你沒有測試實際的ClassA,而是ClassA的一個子類,但是當你使用Mock API時會發生這種情況。

由於PHP7的,你可以利用匿名類是這樣的:

public function testPoodle() { 

    $stub = new class('dog52') extends ClassA { 
     public function getResultFromRemoteServer() { 
      return 'Poodle'; 
     } 
    }; 

    $expected = 'This dog is a Poodle'; 
    $actual = $stub->getFormatted(); 
    $this->assertEquals($expected, $actual); 
} 

PHP7之前,你只寫一個普通類擴展主題被測並使用它的受測試者。或者使用disableOriginalConstructor,如本頁其他地方所示。

+0

我聽到你的聲音,我同意。上面的代碼我只是爲了便於說明而激動起來,但它反映了我正在處理的代碼。 <繼續下一條評論,我繼續按Enter> – jontyc 2011-04-05 07:26:11

+1

我會看到如何彈出遠程調用後適合真正的代碼。這只是其中一個原本沒有課程的情況,因爲它不是必需的,但由於易於測試和嘲笑而增加了它。 – jontyc 2011-04-05 07:33:36

+0

@stebbo如果您有任何其他問題,請隨時通過聊天進行下載。 – Gordon 2011-04-05 07:35:22

41

使用disableOriginalConstructor()使getMock()不會調用構造函數。這個名字有點讓人誤解,因爲調用該方法最終會通過false代替$callOriginalConstructor。這允許您在手動調用構造函數之前在返回的模擬上設置期望值。

$stub = $this->getMockBuilder('ClassA') 
      ->setMethods(array('getResultFromRemoteServer')) 
      ->disableOriginalConstructor() 
      ->getMock(); 
$stub->expects($this->any()) 
    ->method('getResultFromRemoteServer') 
    ->will($this->returnValue('Poodle')); 
$stub->__construct('dog52'); 
... 
+0

聽起來很完美。我可能會按照Gordon的建議進行重組,但我會牢記它。再次感謝大衛。 – jontyc 2011-04-05 07:36:10

+0

B-e-a-utiful!這個確切的問題導致了一系列的錯誤,我縮小了我的模擬方法,而不是從構造函數中調用。這個答案完美地解決了@ jontyc的(和我的)問題。謝謝! – 2011-09-20 03:45:39

+0

正是我所發現的。 – 2016-12-09 13:48:29

相關問題