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

Posted

技术标签:

【中文标题】Zend Framework 2 & PHPUnit - 模拟 Zend\\Db\\Adapter\\Adapter 类【英文标题】:Zend Framework 2 & PHPUnit - mock the Zend\Db\Adapter\Adapter classZend Framework 2 & PHPUnit - 模拟 Zend\Db\Adapter\Adapter 类 【发布时间】:2017-06-27 06:55:12 【问题描述】:

几年前我跟随this tutorial 开始学习Zend 框架。在那里,它显示映射器是使用 Zend\Db\Adapter\Adapter 类创建的以获取数据库连接,这就是我使用数据库的方式,因为没有任何问题。

我现在正在尝试学习如何在 Zend 应用程序上使用 phpUnit,但在测试映射器中的函数时遇到了困难,因为我无法模拟 Zend\Db\Adapter\Adapter 类。

Zend 网站上的This tutorial 显示模拟数据库连接,但它使用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 会出现以下错误:

致命错误:调用 C:\Program Files (x86)\Zend\Apache2\htdocs\test_project\vendor\zendframework\zend-db\src\Sql\Sql.php 中的成员函数 createStatement() on null第 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:

根据 Wilt 的回答,我将映射器更改为在构造函数中使用 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());

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

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

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


编辑#2:

按照 Wilt 的建议,我将测试类更改为使用 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,但是我可能错了!

【问题讨论】:

【参考方案1】:

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

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

现在,您在 Sql 实例上调用 $this->sql->prepareStatementForSqlObject($select);,该实例在内部调用 Adapter 类的 createStatement 方法(您可以看到 here on line 128 inside the Sql class)。但在您的情况下,Adapter 是一个模拟,这就是引发错误的原因:

致命错误:在 128

行的 C:\Program Files (x86)\Zend\Apache2\htdocs\test_project\vendor\zendframework\zend-db\src\Sql\Sql.php 中调用成员函数 createStatement()

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

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

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

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

【讨论】:

很好的答案,谢谢,指出我正确的方向,将依赖项更改为使用Sql 实例。我现在已经摆脱了错误消息,但是测试本身在我期望它通过的地方失败了。如果您不介意看一下,我已经在问题中添加了新代码?我不确定我是否嘲笑了一些错误,或者只是我缺乏单元测试经验,我错误地假设测试通过了! @Crazyloonybin 使用StatementInterface 来模拟语句就足够了(不需要特定的适配器类)。然后你需要在这个$mockStatement中模拟execute方法,这个方法的返回值应该是$resultsSet。那么你的测试应该通过了。 我已将声明更改为$this->mockStatement = $this->getMock('Zend\Db\Adapter\Driver\StatementInterface');,但我仍然遇到同样的错误。我保持$this->mockStatement 代码与问题编辑中显示的相同,testFetchAllProducts 函数返回$resultsSet - 这条线是否也需要修改? 这应该是 execute 函数而不是 testFetchAllProducts 函数 - 我等了太久才编辑! @crazyloonybin 测试应该是assertEqual() 而不是assertSame()。结果集 instance 将始终与返回的方法不同,因为您无法模拟 $resultset = new HydratingResultSet($hydrator, $product);。每次都是一个新实例。但是,如果您将结果集的创建封装在另一个方法 $resultSet = $this->createNewResultSet($hydrator, $entity); 中,那么您将能够存根该方法并使用 assertSame($resultSet, $productMapper->fetchAllProducts()) 进行测试。

以上是关于Zend Framework 2 & PHPUnit - 模拟 Zend\Db\Adapter\Adapter 类的主要内容,如果未能解决你的问题,请参考以下文章

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

Zend Framework 2 on 1&1

Zend Framework windows8.1下配置zend framework1.12

Zend Framework 2 入门

文件上传表单的 Zend 验证器大小 - Zend Framework 2.3

文件上传表单的 Zend 验证器大小 - Zend Framework 2.3