Zend Framework 2和PHPUnit - 模拟Zend\Db\Adapter\Adapter类

cra*_*bin 6 phpunit unit-testing zend-framework mocking zend-framework2

几年前,我按照本教程开始学习Zend Framework .在那里,它显示使用Zend\Db\Adapter\Adapter类创建映射器以获取数据库连接,这就是我使用数据库的方式,因为没有问题.

我现在正在尝试学习如何在Zend应用程序上使用PHPUnit,并且在测试mapper中的函数时遇到了困难,因为我无法模拟Zend\Db\Adapter\Adapter类.

Zend网站上的本教程展示了模拟数据库连接,但它使用了Zend\Db\TableGateway\TableGateway该类.我在网上找到的所有其他教程使用此类过了,我就发现的唯一Zend\Db\Adapter\Adapter类是这样:

$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));
Run Code Online (Sandbox Code Playgroud)

我已经尝试将它放入我的setUp方法但phpunit在测试类上运行会给我以下错误:

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

所以我的问题是,你如何Zend\Db\Adapter\Adapter在PHPUnit中模拟类?

我已经看到了这个类似的问题,但是使用了一个Zend/Db/Adapter/AdapterInterface代替,我似乎无法将该代码转换成我的情况.Mapper和测试类代码如下.

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;
}
Run Code Online (Sandbox Code Playgroud)

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());
}
Run Code Online (Sandbox Code Playgroud)

编辑#1:

继Wilt的回答之后,我改变了我的mapper以Sql在构造函数中使用该类并将我的Test类更改为:

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());
}
Run Code Online (Sandbox Code Playgroud)

但是,我现在收到以下错误:

ProductTest\Model\ProductMapperTest :: testFetchAllProducts断言两个变量引用同一个对象失败.

哪个来自这条线$this->assertSame($resultsSet, $productMapper->fetchAllProducts());.我是否错误地嘲笑了什么?


编辑#2:

正如Wilt所建议的那样,我改变了测试类来使用a 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());
}
Run Code Online (Sandbox Code Playgroud)

但是测试用例仍然如上所述失败.我没有改变嘲弄execute方法的代码行,因为我相信它已经返回了$resultsSet,但是我可能错了!

Wil*_*ilt 3

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

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

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

致命错误:第128createStatement()对 null调用成员函数C:\Program Files (x86)\Zend\Apache2\htdocs\test_project\vendor\zendframework\zend-db\src\Sql\Sql.php

因此,为了解决这个问题,你应该模拟这个方法,就像你对该query方法所做的那样:

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

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

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