PHPUnit - Mock PDO Statement fetch
Still in the process of testing a mapper class, I need to mock PDO. But now I ran into an infinite loop problem:
$arrResult = array(
array('id'=>10, 'name'=>'abc'),
array('id'=>11, 'name'=>'def'),
array('id'=>12, 'name'=>'ghi')
);
$STMTstub->expects($this->any())
->method('fetch')
->will($this->returnValue($arrResult));
$PDOstub = $this->getMock('mockPDO');
$PDOstub->expects($this->开发者_开发百科any())
->method('prepare')
->will($this->returnValue($STMTstub));
Of course, that code is perfect when it come to test 1 fetch or a fetchAll. But when it come to multiple fetch, the infinite loop happen. Like in that case:
while($arr = $stmt->fetch()){
//...
}
So I'd like the fetch() to loop through all the $arrResult and return the sub-array one by one to simulate the true fetch() behavior. Can I "hook a function" to do so or something ?
You have two options:
- For a few results you can use
at()
to order the returned values, or - for more results you can use
returnCallback()
to call a function that yields the desired results across multiple calls.
Using at()
is pretty straight-forward. You pass in an index that PHPUnit matches up against the calls to pick which expectation to fire. Note that the index passed to at()
is across all calls to the mock. If $STMTstub
will receive other mocked calls between the calls to fetch()
, you'll need to adjust the indexes accordingly.
$STMTstub->expects($this->at(0))
->method('fetch')
->will($this->returnValue($arrResult[0]));
$STMTstub->expects($this->at(1))
->method('fetch')
->will($this->returnValue($arrResult[1]));
$STMTstub->expects($this->at(2))
->method('fetch')
->will($this->returnValue($arrResult[2]));
Using returnCallback()
will require a bit more scaffolding, but it avoids all the index shenanigans. ;)
public static function yieldResults($name, $results) {
static $indexes = array();
if (isset($indexes[$name])) {
$index = $indexes[$name] + 1;
}
else {
$index = 0;
}
self::assertLessThan(count($results), $index);
$indexes[$name] = $index;
return $results[$index];
}
public function testMyPdo() {
$STMTmock = ...
$STMTmock->expects($this->any())->method('fetch')
->will($this->returnCallback(function() {
return self::yieldResults('testMyPdo',
array(
array('id'=>10, 'name'=>'abc'),
array('id'=>11, 'name'=>'def'),
array('id'=>12, 'name'=>'ghi'),
);});
}
yieldResults()
is generic and will work with any number of simultaneous result sets as long as you give each a unique $name
. If you're not using PHP 5.3 with callbacks, wrap the call to yieldResults()
inside another function whose name you pass to returnCallback()
. I haven't tested it out, but it seems fairly sound.
精彩评论