Zend Framework 2 & PHPUnit - mock the Zend\Db\Adapter\Adapter class



几年前,我开始学习Zend Framework,学习本教程。在那里,它显示了使用ZendDbAdapterAdapter类创建映射器以获得数据库连接,这就是我从那以后处理数据库的方式,没有任何问题。

我现在正试图学习如何在Zend应用程序上使用PHPUnit,并且在测试映射器中的函数时遇到了困难,因为我无法模拟ZendDbAdapterAdapter类。

Zend网站上的本教程展示了模拟数据库连接,但它使用了ZendDbTableGatewayTableGateway类。我在网上找到的所有其他教程也使用这个类,我发现的关于ZendDbAdapterAdapter类的唯一东西是:

$date = new DateTime();
$mockStatement = $this->getMock('ZendDbAdapterDriverPdoStatement');
$mockStatement->expects($this->once())->method('execute')->with($this->equalTo(array(
'timestamp' => $date->format(FormatterInterface::DEFAULT_DATETIME_FORMAT)
)));
$mockDbDriver = $this->getMockBuilder('ZendDbAdapterDriverPdoPdo')
->disableOriginalConstructor()
->getMock();
$mockDbAdapter = $this->getMock('ZendDbAdapterAdapter', array(), array($mockDbDriver));
$mockDbAdapter->expects($this->once())
->method('query')
->will($this->returnValue($mockStatement));

我已经尝试将其放入setUp方法中,但在测试类上运行phpunit会出现以下错误:

致命错误:在C:\Program Files(x86)\Zend\Apache2\htdocs\test_project\vendor\zendframework\Zend db\src\Sql\Sql.php中的null处调用成员函数createStatement()

所以我的问题是,如何在PHPUnit中模拟ZendDbAdapterAdapter

我看到了这个类似的问题,但使用了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('ZendDbAdapterDriverPdoStatement');
$mockStatement->expects($this->once())->method('execute')->with($this->equalTo(array(
'timestamp' => $date->format(FormatterInterface::DEFAULT_DATETIME_FORMAT)
)));
$mockDbDriver = $this->getMockBuilder('ZendDbAdapterDriverPdoPdo')->disableOriginalConstructor()->getMock();
$this->mockDbAdapter = $this->getMock('ZendDbAdapterAdapter', 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:

根据Wilt的回答,我将映射器更改为在构造函数中使用Sql类,并将测试类更改为:

public function setUp() {
$mockSelect = $this->getMock('ZendDbSqlSelect');
$mockDbAdapter = $this->getMockBuilder('ZendDbAdapterAdapterInterface')->disableOriginalConstructor()->getMock();
$this->mockStatement = $this->getMock('ZendDbAdapterDriverPdoStatement');
$this->mockSql = $this->getMock('ZendDbSqlSql', 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断言两个变量引用同一对象失败。

它来自$this->assertSame($resultsSet, $productMapper->fetchAllProducts());行。我是不是嘲笑错了什么?


编辑#2:

正如Wilt所建议的,我将测试类改为使用StatementInterface来模拟语句,因此代码现在看起来像:

public function setUp() {
$mockSelect = $this->getMock('ZendDbSqlSelect');
$mockDbAdapter = $this->getMockBuilder('ZendDbAdapterAdapterInterface')->disableOriginalConstructor()->getMock();
$this->mockStatement = $this->getMock('ZendDbAdapterDriverStatementInterface');
$this->mockSql = $this->getMock('ZendDbSqlSql', 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,但我可能错了!

也许这里最好更改__construct方法,将Sql实例作为参数。$dbAdapter似乎只在构造函数内部使用,因此在我看来,ProductMapper类的实际依赖项不是Adapter实例,而是Sql实例。如果您进行了更改,您只需要模拟ProductMapperTest中的Sql类。

如果您不想在代码中进行这样的更改,并且仍然想继续为当前ProductMapper类编写测试,那么还应该模拟Sql类内部调用的Adapter类的所有其他方法。

现在,您在Sql实例上调用$this->sql->prepareStatementForSqlObject($select);,该实例在内部调用Adapter类的createStatement方法(您可以在Sql类内部的128行中看到)。但在您的情况下,Adapter是一个mock,这就是抛出错误的原因:

致命错误:在第128行的C:Program Files (x86)ZendApache2htdocstest_projectvendorzendframeworkzend-dbsrcSqlSql.php中对成员函数createStatement()的null调用

所以要解决这个问题,你应该模仿这个方法,就像你对query方法所做的那样:

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

在下一行中,您调用$statement->execute();,这意味着您还需要模拟$mockStatement中的execute方法。

正如您所看到的,这个测试变得相当麻烦。你应该问问自己,你是否走在正确的道路上,测试正确的组件。您可以进行一些小的设计更改(改进),使测试ProductMapper类变得更容易。

相关内容

  • 没有找到相关文章

最新更新