6

幾年前,我開始學習Zend Framework之後的this tutorial。在那裏,它顯示了使用Zend\Db\Adapter\Adapter類創建映射器來獲得數據庫連接,這就是我使用數據庫的方式,因爲沒有問題。Zend Framework 2&PHPUnit - 模擬Zend Db Adapter Adapter類

我現在正試圖學習如何在Zend應用程序上使用PHPUnit,並且因爲我無法模擬Zend\Db\Adapter\Adapter類而在測試映射器中的函數時遇到了困難。

This tutorial在Zend網站上顯示模擬數據庫連接,但它使用Zend\Db\TableGateway\TableGateway類。我在網上找到的所有其他教程使用此類過了,唯一的事情,我發現關於Zend\Db\Adapter\Adapter類是this

$date = new DateTime(); 
$mockStatement = $this->getMock('Zend\Db\Adapter\Driver\Pdo\Statement'); 
$mockStatement->expects($this->once())->method('execute')->with($this->equalTo(array(
    'timestamp' => $date->format(FormatterInterface::DEFAULT_DATETIME_FORMAT) 
))); 

$mockDbDriver = $this->getMockBuilder('Zend\Db\Adapter\Driver\Pdo\Pdo') 
    ->disableOriginalConstructor() 
    ->getMock(); 

$mockDbAdapter = $this->getMock('Zend\Db\Adapter\Adapter', array(), array($mockDbDriver)); 
$mockDbAdapter->expects($this->once()) 
    ->method('query') 
    ->will($this->returnValue($mockStatement)); 

我試圖把該爲我的setUp的方法,但在運行phpunit測試類給了我以下錯誤:

Fatal error: Call to a member function createStatement() on null in C:\Program Files (x86)\Zend\Apache2\htdocs\test_project\vendor\zendframework\zend-db\src\Sql\Sql.php on line 128

所以我的問題是,你怎麼嘲笑了PHPUnit中Zend\Db\Adapter\Adapter類?

我見過this question這是類似的,但採用的是Zend/Db/Adapter/AdapterInterface相反,我似乎無法代碼翻譯成我的情況。映射器和測試類代碼如下。

ProductMapper.php:

public function __construct(Adapter $dbAdapter) { 
    $this->dbAdapter = $dbAdapter; 
    $this->sql = new Sql($dbAdapter); 
} 

public function fetchAllProducts() { 
    $select = $this->sql->select('products'); 

    $statement = $this->sql->prepareStatementForSqlObject($select); 
    $results = $statement->execute(); 

    $hydrator = new ClassMethods(); 
    $product = new ProductEntity(); 
    $resultset = new HydratingResultSet($hydrator, $product); 
    $resultset->initialize($results); 
    $resultset->buffer(); 

    return $resultset; 
} 

ProductMapperTest.php:

public function setUp() { 
    $date = new DateTime(); 

    $mockStatement = $this->getMock('Zend\Db\Adapter\Driver\Pdo\Statement'); 
    $mockStatement->expects($this->once())->method('execute')->with($this->equalTo(array(
      'timestamp' => $date->format(FormatterInterface::DEFAULT_DATETIME_FORMAT) 
    ))); 

    $mockDbDriver = $this->getMockBuilder('Zend\Db\Adapter\Driver\Pdo\Pdo')->disableOriginalConstructor()->getMock(); 

    $this->mockDbAdapter = $this->getMock('Zend\Db\Adapter\Adapter', array(), array(
      $mockDbDriver 
    )); 
    $this->mockDbAdapter->expects($this->once())->method('query')->will($this->returnValue($mockStatement)); 
} 

public function testFetchAllProducts() { 
    $resultsSet = new ResultSet(); 

    $productMapper = new ProductMapper($this->mockDbAdapter); 

    $this->assertSame($resultsSet, $productMapper->fetchAllProducts()); 
} 

編輯#1:

關注從張伯倫的回答荷蘭國際集團,我改變了我的映射器使用Sql類的構造函數,並改變了我的測試類:

public function setUp() { 
    $mockSelect = $this->getMock('Zend\Db\Sql\Select'); 

    $mockDbAdapter = $this->getMockBuilder('Zend\Db\Adapter\AdapterInterface')->disableOriginalConstructor()->getMock(); 

    $this->mockStatement = $this->getMock('Zend\Db\Adapter\Driver\Pdo\Statement'); 

    $this->mockSql = $this->getMock('Zend\Db\Sql\Sql', array('select', 'prepareStatementForSqlObject'), array($mockDbAdapter)); 
    $this->mockSql->method('select')->will($this->returnValue($mockSelect)); 
    $this->mockSql->method('prepareStatementForSqlObject')->will($this->returnValue($this->mockStatement)); 
} 

public function testFetchAllProducts() { 
    $resultsSet = new ResultSet(); 

    $this->mockStatement->expects($this->once())->method('execute')->with()->will($this->returnValue($resultsSet)); 

    $productMapper = new ProductMapper($this->mockSql); 

    $this->assertSame($resultsSet, $productMapper->fetchAllProducts()); 
} 

不過,現在我得到以下錯誤:

ProductTest\Model\ProductMapperTest::testFetchAllProducts Failed asserting that two variables reference the same object.

哪個來自$this->assertSame($resultsSet, $productMapper->fetchAllProducts());。我是否歪曲了一些不正確的東西?


編輯#2:

正如威爾特建議,我改變了測試類使用StatementInterface嘲笑一個語句來代替,所以代碼現在看起來像:

public function setUp() { 

    $mockSelect = $this->getMock('Zend\Db\Sql\Select'); 

    $mockDbAdapter = $this->getMockBuilder('Zend\Db\Adapter\AdapterInterface')->disableOriginalConstructor()->getMock(); 

    $this->mockStatement = $this->getMock('Zend\Db\Adapter\Driver\StatementInterface'); 

    $this->mockSql = $this->getMock('Zend\Db\Sql\Sql', array('select', 'prepareStatementForSqlObject'), array($mockDbAdapter)); 
    $this->mockSql->method('select')->will($this->returnValue($mockSelect)); 
    $this->mockSql->method('prepareStatementForSqlObject')->will($this->returnValue($this->mockStatement)); 

} 

public function testFetchAllProducts() { 
    $resultsSet = new ResultSet(); 

    $this->mockStatement->expects($this->once())->method('execute')->with()->will($this->returnValue($resultsSet)); 

    $productMapper = new ProductMapper($this->mockSql); 

    $this->assertSame($resultsSet, $productMapper->fetchAllProducts()); 
} 

但是測試用例仍然如上所述失敗。我沒有改變嘲弄execute方法的代碼行,因爲我相信它已經返回$resultsSet,但是我可能是錯的!

回答

3

也許最好在這裏更改__construct方法以Sql實例作爲參數。看起來$dbAdapter只在構造函數中使用,因此在我看來,ProductMapper類的實際依賴關係不是Adapter實例,而是Sql實例。如果你做了這個改變,你只需要在ProductMapperTest內模擬Sql類。

如果你不想讓你的代碼中的這種變化,你還是要繼續編寫測試當前ProductMapper類,你也應該嘲笑Adapter類的所有其他方法了Sql類調​​用內部。

現在你對你的Sql實例,它在內部調用Adapter類的createStatement方法調用$this->sql->prepareStatementForSqlObject($select);(你可以看到,here on line 128 inside the Sql class)。但是,你的情況Adapter是一個模擬的,這就是爲什麼在引發錯誤:

Fatal error: Call to a member function createStatement() on null in C:\Program Files (x86)\Zend\Apache2\htdocs\test_project\vendor\zendframework\zend-db\src\Sql\Sql.php on line 128

因此,要解決這個問題,你應該也同樣嘲笑這種方法,你是如何做到的query方法:

$mockStatement = //...your mocked statement... 
$this->mockDbAdapter->expects($this->once()) 
        ->method('createStatement') 
        ->will($this->returnValue($mockStatement)); 

在下一行中,您可以撥打$statement->execute();,這意味着您還需要在$mockStatement內模擬execute方法。

正如你所看到的寫作這個測試變得非常麻煩。而且你應該問自己,如果你在正確的道路上並測試正確的組件。您可以進行一些小的設計更改(改進),以便測試您的ProductMapper課程。

+0

偉大的答案謝謝,指出我在正確的方向改變依賴使用'Sql'實例。我現在已經擺脫了錯誤信息,但測試本身沒有達到我期望的效果。如果你不介意看看,我已經爲這個問題添加了新的代碼?我不確定我是否嘲笑了某些錯誤,或者僅僅是缺乏單元測試的經驗,並且我錯誤地認爲測試通過了! – crazyloonybin

+0

@Crazyloonybin使用'StatementInterface'來模擬語句就足夠了(不需要爲此需要特定的適配器類)。然後你需要在這個'$ mockStatement'中模擬'execute'方法,並且這個方法的返回值應該是'$ resultsSet'。然後你的測試應該通過。 – Wilt

+0

我已經改變了語句以讀取'$ this-> mockStatement = $ this-> getMock('Zend \ Db \ Adapter \ Driver \ StatementInterface');'但我仍然收到同樣的錯誤。我保留'$ this-> mockStatement'代碼與問題編輯中顯示的代碼相同,'testFetchAllProducts'函數返回'$ resultsSet' - 此行是否也需要修改? – crazyloonybin

相關問題