【问题标题】:PHPUnit - How to mock PDO prepared statementPHPUnit - 如何模拟 PDO 准备好的语句
【发布时间】:2011-07-17 09:16:56
【问题描述】:

我正在尝试使用 PHPUnit 对映射器类进行单元测试。 我可以轻松地模拟将在映射器类中注入的 PDO 实例,但我不知道如何模拟 PreparedStatement 类,因为它是由 PDO 类生成的。

在我的例子中,我扩展了 PDO 类,所以我有这个:

public function __construct($dsn, $user, $pass, $driverOptions)
{

    //...

    parent::__construct($dsn, $user, $pass, $driverOptions);
    $this->setAttribute(PDO::ATTR_STATEMENT_CLASS,
        array('Core_Db_Driver_PDOStatement', array($this)));
}

关键是 Core_Db_Driver_PDOStatement 没有注入到 PDO 类的构造函数中,它是静态实例化的。即使我这样做:

public function __construct($dsn, $user, $pass, $driverOptions, $stmtClass = 'Core_Db_Driver_PDOStatement')
{

    //...

    parent::__construct($dsn, $user, $pass, $driverOptions);
    $this->setAttribute(PDO::ATTR_STATEMENT_CLASS,
        array($stmtClass, array($this)));
}

...它仍然是一个静态实例,因为我无法传递我自己的准备好的语句类的模拟实例。

有什么想法吗?

编辑: 解决方案,改编自anwser:

/**
 * @codeCoverageIgnore
 */
private function getDbStub($result)
{
    $STMTstub = $this->getMock('PDOStatement');
    $STMTstub->expects($this->any())
            ->method('fetchAll')
            ->will($this->returnValue($result));


    $PDOstub = $this->getMock('mockPDO');
    $PDOstub->expects($this->any())
            ->method('prepare')
            ->will($this->returnValue($STMTstub));

    return $PDOstub;
}

public function testGetFooById()
{
    $arrResult = array( ... );
    $PDOstub = $this->getDbStub($arrResult);
}

【问题讨论】:

    标签: php mocking pdo phpunit prepared-statement


    【解决方案1】:

    如果您可以模拟 PDO 类,只需模拟出 pdo 类及其所有依赖项。无需关心语句类或 pdo 类的构造函数,因为您通过模拟定义了输入和输出。

    所以你需要一个返回模拟对象的模拟对象。

    这可能看起来有点令人困惑,但由于您应该只测试正在测试的类的功能,而没有其他任何东西,您几乎可以摆脱数据库连接的所有其他部分。

    在这个例子中,你想知道的是:

    • 准备调用了吗?
    • fetchAll 是否在 prepare 返回时调用?
    • 是否返回了该调用的结果?

    如果是这样:很好。

    <?php
    class myClass {
         public function __construct(ThePDOObject $pdo) {
             $this->db = $pdo;
         }
    
         public function doStuff() {
             $x = $this->db->prepare("...");
             return $x->fetchAll();
         }
    }
    
    class myClassTest extends PHPUnit_Framework_TestCase {
    
        public function testDoStuff() {
    
            $fetchAllMock = $this
               ->getMockBuilder("stdClass" /* or whatever has a fetchAll */)
               ->setMethods(array("fetchAll"))
               ->getMock();
            $fetchAllMock
               ->expects($this->once())->method("fetchAll")
               ->will($this->returnValue("hello!"));
    
            $mock = $this
               ->getMockBuilder("ThePDOObject")
               ->disableOriginalConstructor()
               ->setMethods(array("prepare"))
               ->getMock();
            $mock
               ->expects($this->once())
               ->method("prepare")
               ->with("...")
               ->will($this->returnValue($fetchAllMock));
    
            $x = new myClass($mock);
            $this->assertSame("hello!", $x->doStuff());
    
    
        }
    
    }
    

    【讨论】:

    • 没错,您确实找到了我不太了解的部分:当您可以伪造 fetchAll 将返回您可以在测试中决定的内容时,无需注入 Statement。我已经编辑了我的问题以放置我适应的解决方案。谢谢!
    • 这种方法的问题在于它将应用程序代码实现暴露给测试。测试是否应该知道应用程序正在调用 ->fetch() 还是 fetchAll()?一点也不!如果代码适应使用 fetch() 而不是 fetchaAll(),该方法可能仍然可以正常工作,但测试会失败。
    • @TomB,这是模拟依赖项的一般问题,而不仅仅是这个答案。
    猜你喜欢
    • 1970-01-01
    • 2020-03-26
    • 2010-11-30
    • 1970-01-01
    • 2011-07-19
    • 2011-07-13
    • 2017-09-29
    相关资源
    最近更新 更多