Edited at

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

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

https://php.net/pdo.query


<?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でいいよね。