素のPDOとPDOStatementでもスタブ・モックでテストしたい


本格的なDBのテストはdbunitを使うんだけど、DBを含めたテストじゃなくて何らかのデータがあれば良くて、でもORM的なものは使ってないからデータ取得メソッドのスタブを作りづらい場合。ちょっとしたPDOの処理もcreateMockで簡単にテスト出来ないもんだろうか。


<?php

namespace Sample;

use PDO;
use PDOStatement;

class Sample
{
    private function parseRows(PDOStatement $stmt){
        $ret = [];
        foreach ($stmt as $row) {
            $ret[] = $row['name']
                   . "\t" . $row['color']
                   . "\t" . $row['calories']
                   . "\n";
        }
        return $ret;
    }

    public function getFruit(PDO $conn) {
        $sql = 'SELECT name, color, calories FROM fruit ORDER BY name';
        $stmt = $conn->query($sql);
        return $this->parseRows($stmt);
    }
}

マニュアルのコードから持ってきた。
PDOを直接使ってる簡単なコードと思いきや、型がしっかり書いてあったりして。
ちょっとしたキーバリューデータがメインのロジックに少し入っているだけといった場合に。

<?php

declare(strict_types=1);

namespace Sample;

use PHPUnit\Framework\TestCase;
use PDO;
use PDOStatement;
use IteratorAggregate;
use ArrayIterator;

final class SmapleTest extends TestCase
{
    public function testGetFruit()
    {
        $stub = $this->getMockBuilder(PDO::class)
              ->disableOriginalConstructor()
              ->disableArgumentCloning()
              ->disallowMockingUnknownTypes()
              ->getMock();
        $stub->method('query')
            ->willReturn(new class extends PDOStatement implements IteratorAggregate {
                    function getIterator(){
                        return new ArrayIterator([
                            [
                                'name' => 'apple',
                                'color' => 'red',
                                'calories' => 150,
                            ],
                            [
                                'name' => 'banana',
                                'color' => 'yellow',
                                'calories' => 250,
                            ],
                        ]);
                    }
                });

        $target = new Sample();
        $ret = $target->getFruit($stub);

        $this->assertEquals("apple\tred\t150\n", $ret[0]);
        $this->assertEquals("banana\tyellow\t250\n", $ret[1]);
    }
}

そいうときはPDOの返却値でPDOStatementを継承したクラスを返せばいける。

PDOStatementTraversableを直接実装しているというPHPのコードでは有り得ない状態になっているんだけど、IteratorAggregateを実装すれば問題なくイテレーターを置き換えられるみたい。

検索したらPDOStatementを継承したテスト専用モッククラスを作るとか見かけたけども、今ならnew classでいいよね。