DEV Community

Recca Tsai
Recca Tsai

Posted on • Originally published at recca0120.github.io

Mock IteratorAggregate with Mockery to Fix foreach in Tests

Originally published at recca0120.github.io

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:

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";
}
Enter fullscreen mode Exit fullscreen mode

How to Mock It

Just mock getIterator() to return an ArrayObject:

composer require --dev mockery/mockery
Enter fullscreen mode Exit fullscreen mode
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";
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Three things are all you need: the class implements IteratorAggregate, mock getIterator, and return an ArrayObject.

Top comments (0)