Zend Framework 2 和 PHPUnit - 模拟 Zend\Db\Adapter\Adapter class
Zend Framework 2 & PHPUnit - mock the Zend\Db\Adapter\Adapter class
我在几年前开始学习 Zend Framework this tutorial。在那里,它显示映射器是使用 Zend\Db\Adapter\Adapter
class 创建的以获取数据库连接,这就是我使用数据库的方式,因为没有任何问题。
我现在正在尝试学习如何在 Zend 应用程序上使用 PHPUnit,并且 运行 在测试映射器中的函数时遇到困难,因为我无法模拟 Zend\Db\Adapter\Adapter
class.
This tutorial on the Zend website shows mocking database connections, but it uses the Zend\Db\TableGateway\TableGateway
class. All other tutorials I've found online use this class too, and the only thing I've found regarding the Zend\Db\Adapter\Adapter
class is 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
在测试 class 时出现以下错误:
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
class?
我见过类似的 ,但使用的是 Zend/Db/Adapter/AdapterInterface
,我似乎无法将该代码转换为我的情况。下面的映射器和测试 class 代码。
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:
根据 Wilt 的回答,我将映射器更改为在构造函数中使用 Sql
class,并将测试 class 更改为:
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:
根据 Wilt 的建议,我将测试 class 更改为使用 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
,但我可能错了!
也许在这里更改 __construct
方法以将 Sql
实例作为参数会更好。似乎 $dbAdapter
只在构造函数内部使用,因此在我看来你的 ProductMapper
class 的实际依赖不是 Adapter
实例,但是而是一个 Sql
实例。如果您进行了更改,则只需在 ProductMapperTest
.
中模拟 Sql
class
如果您不想在您的代码中进行这样的更改,并且您仍想继续为当前 ProductMapper
class 编写测试,您还应该模拟所有其他方法Adapter
class Sql
class 正在内部调用。
现在您在 Sql
实例上调用 $this->sql->prepareStatementForSqlObject($select);
,它在内部调用 Adapter
class 的 createStatement
方法(您可以看到 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
class.
我在几年前开始学习 Zend Framework this tutorial。在那里,它显示映射器是使用 Zend\Db\Adapter\Adapter
class 创建的以获取数据库连接,这就是我使用数据库的方式,因为没有任何问题。
我现在正在尝试学习如何在 Zend 应用程序上使用 PHPUnit,并且 运行 在测试映射器中的函数时遇到困难,因为我无法模拟 Zend\Db\Adapter\Adapter
class.
This tutorial on the Zend website shows mocking database connections, but it uses the Zend\Db\TableGateway\TableGateway
class. All other tutorials I've found online use this class too, and the only thing I've found regarding the Zend\Db\Adapter\Adapter
class is 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
在测试 class 时出现以下错误:
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
class?
我见过类似的 Zend/Db/Adapter/AdapterInterface
,我似乎无法将该代码转换为我的情况。下面的映射器和测试 class 代码。
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:
根据 Wilt 的回答,我将映射器更改为在构造函数中使用 Sql
class,并将测试 class 更改为:
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:
根据 Wilt 的建议,我将测试 class 更改为使用 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
,但我可能错了!
也许在这里更改 __construct
方法以将 Sql
实例作为参数会更好。似乎 $dbAdapter
只在构造函数内部使用,因此在我看来你的 ProductMapper
class 的实际依赖不是 Adapter
实例,但是而是一个 Sql
实例。如果您进行了更改,则只需在 ProductMapperTest
.
Sql
class
如果您不想在您的代码中进行这样的更改,并且您仍想继续为当前 ProductMapper
class 编写测试,您还应该模拟所有其他方法Adapter
class Sql
class 正在内部调用。
现在您在 Sql
实例上调用 $this->sql->prepareStatementForSqlObject($select);
,它在内部调用 Adapter
class 的 createStatement
方法(您可以看到 here on line 128 inside the Sql
class).但在你的情况下 Adapter
是一个模拟,这就是抛出错误的原因:
Fatal error: Call to a member function
createStatement()
on null inC:\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
class.