Nigel Ren
Nigel Ren

Reputation: 57121

Testing coverage with PHPUnit and PDO

As an exercise, I'm trying to reach 100% code coverage using PHPUnit, this is almost there, but it's the error bits which I've found difficult.

I have an example class which I has a similar issue to my real code, the exact code is not the issue, but the behaviour is the same.

class DBAccess  {
    private $db;

    public function __construct( \PDO $db ) {
        $this->db = $db;
    }

    public function select ()   {
        $data = false;
        if ( $stmt = $this->db->prepare("select * from user") ){
            if ( $stmt->execute() ){
                $data = $stmt->fetchAll();
            }
            else    {
                echo "Something went wrong";  // How to get here!
            }
        }
        return $data;
    }
}

It's the one line which is stopping the 100% coverage. The difficulty is that I need the prepare to pass, but the execute to fail. I've managed this on updates by passing invalid foreign keys or fields to large for the table, but with a select, I'm not sure what can go wrong.

My test is...

class DBAccessTest extends TestCase {
    private $pdo;

    public function setUp() {
        $this->pdo = new PDO("mysql:host=172.18.0.2;dbname=test",
                "test", "RkqwD1gjOdjkrwTt");
        $this->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
    }

    public function testSelect()    {
        $test = new DBAccess( $this->pdo );
        $this->assertTrue($test->select() != false );
    }
}

There are two possibilities I can think of

  1. Assume that if the prepare has worked, that the execute will as well, but not particularly happy with that.
  2. Somehow override the connection to make it fail in a controlled way.

Upvotes: 0

Views: 1975

Answers (1)

Jeff Puckett
Jeff Puckett

Reputation: 40861

This is exactly what PHPUnit's test doubles are for. And kudos for designing your classes in an object oriented way, so it's easy to inject mocks into them.

<?php

use PHPUnit\Framework\TestCase;

class DBAccessTest extends TestCase
{
    public function test_something_goes_wrong()
    {
        // Create test doubles.
        $stmtMock = $this->createMock(\PDOStatement::class);
        $pdoMock = $this->createMock(\PDO::class);

        // Configure the stubs.
        $stmtMock->method('execute')
                ->willReturn(false);
        $pdoMock->method('prepare')
                ->willReturn($stmtMock);

        // Inject the mock.
        $test = new DBAccess($pdoMock);

        // Assert.
        $this->assertFalse($test->select());
    }
}

Upvotes: 3

Related Questions