While developing Google Firebase-related code, I needed to mock QuerySnapshot, which implements IteratorAggregate. It wasn’t immediately obvious how to make foreach work with Mockery.
What is IteratorAggregate
IteratorAggregate is a built-in PHP interface. By implementing it and providing a getIterator() method, an object becomes iterable with foreach. The key point: foreach iterates over whatever getIterator() returns, not the object itself – which is exactly what makes it mockable:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| class myData implements IteratorAggregate {
public $property1 = "Public property one";
public $property2 = "Public property two";
public $property3 = "Public property three";
public function __construct() {
$this->property4 = "last property";
}
public function getIterator() {
return new ArrayIterator($this);
}
}
$obj = new myData;
foreach($obj as $key => $value) {
var_dump($key, $value);
echo "\n";
}
|
How to Mock It
Just mock getIterator() to return an ArrayObject:
1
| composer require --dev mockery/mockery
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| use ArrayObject;
use IteratorAggregate;
use Mockery;
use PHPUnit\Framework\TestCase;
class IteratorAggregateTest extends TestCase
{
public function test_mock_aggregate(): void
{
$iterator = Mockery::mock(IteratorAggregate::class);
$iterator->allows('getIterator')
->andReturn(new ArrayObject(['foo', 'bar']));
foreach ($iterator as $value) {
echo $value."\n";
}
}
}
|
Three things are all you need: the class implements IteratorAggregate, mock getIterator, and return an ArrayObject.