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.