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