Featured image of post Mock IteratorAggregate with Mockery to Fix foreach in Tests

Mock IteratorAggregate with Mockery to Fix foreach in Tests

Mocking IteratorAggregate breaks foreach in PHPUnit. Return an ArrayObject from the mocked getIterator method to make foreach iterate over test data correctly.

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.