Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHPUnit - How to mock PDO prepared statement

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);
}
like image 856
FMaz008 Avatar asked Mar 17 '11 12:03

FMaz008


1 Answers

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


    }

}
like image 142
edorian Avatar answered Sep 28 '22 19:09

edorian