PHPUnit - 如何模拟 PDO 准备好的语句

发布于 2024-10-23 13:06:03 字数 1498 浏览 4 评论 0原文

我正在尝试使用 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);
}

I'm trying to unit test a mapper class with PHPUnit.
I can easilly mock the PDO instance that will be injected in the mapper class, but I can't figure out how to mock the PreparedStatement class, as it's generated by the PDO class.

In my case I've extended the PDO class, so I have this:

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)));
}

The point is that Core_Db_Driver_PDOStatement is not injected in the constructor of the PDO Class, it's instanciated statically. And even if I do this:

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)));
}

... it's still a static instanciation as I can't pass my own mocked instance of the prepared statement class.

Any idea ?

Edit:
Solution, adapted from the 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);
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

尽揽少女心 2024-10-30 13:06:03

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

因此,您需要一个返回模拟对象的模拟对象。

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

在这个例子中,您想要弄清楚的是:

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

如果是这样:一切都好。

<?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());


    }

}

If you can mock the PDO class just mock out the pdo class and all it's dependencies. There should be no need to care about the statement class or the constructor of the pdo class since you define the input and output via the mocks.

So you need a mock object that returns a mock object.

It might look a little confusing but since you should only test what the class under testing does and nothing else you can pretty much make away with all the other parts of your DB connection.

In this example all you want to figure out is:

  • Is prepare called?
  • Is fetchAll called on what prepare returns?
  • Is the result of that call returned?

If so: All well.

<?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());


    }

}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文