How can I mock a class that implements the Iterator interface using PHPUnit?

Here's a solution which combines the best of both worlds, using an ArrayIterator internally:

With phpunit/phpunit mocking facilities

/**
 * @return \PHPUnit_Framework_MockObject_MockObject|SomeIterator
 */
private function createSomeIteratorDouble(array $items = []): SomeIterator
{
    $someIterator = $this->createMock(SomeIterator::class);

    $iterator = new \ArrayIterator($items);

    $someIterator
        ->method('rewind')
        ->willReturnCallback(function () use ($iterator): void {
            $iterator->rewind();
        });

    $someIterator
        ->method('current')
        ->willReturnCallback(function () use ($iterator) {
            return $iterator->current();
        });

    $someIterator
        ->method('key')
        ->willReturnCallback(function () use ($iterator) {
            return $iterator->key();
        });

    $someIterator
        ->method('next')
        ->willReturnCallback(function () use ($iterator): void {
            $iterator->next();
        });

    $someIterator
        ->method('valid')
        ->willReturnCallback(function () use ($iterator): bool {
            return $iterator->valid();
        });

    return $someIterator;
}

With phpspec/prophecy

/**
 * @return \PHPUnit_Framework_MockObject_MockObject|SomeIterator
 */
private function createSomeIteratorDouble(array $items = []): SomeIterator
{
    $someIterator = $this->prophesize(\ArrayIterator::class);

    $iterator = new \ArrayIterator($items);

    $someIterator
        ->rewind()
        ->will(function () use ($iterator): void {
            $iterator->rewind();
        });

    $someIterator
        ->current()
        ->will(function () use ($iterator) {
            return $iterator->current();
        });

    $someIterator
        ->key()
        ->will(function () use ($iterator) {
            return $iterator->key();
        });

    $someIterator
        ->next()
        ->will(function () use ($iterator): void {
            $iterator->next();
        });

    $someIterator
        ->valid()
        ->will(function () use ($iterator): bool {
            return $iterator->valid();
        });

    return $someIterator->reveal();
}

There's a couple of existing solutions to this problem online already but all of the ones I've seen share a similar weakness: they rely on ->expects($this->at(n)). The 'expects at' function in PHPUnit has slightly odd behaviour in that the counter is for every method call to the mock. This means that if you have method calls to your iterator outside of a straight forward foreach you have to adjust your iterator mock.

The solution to this is to create an object holding the basic iterator data (source array and position) and pass that into returnCallback closures. Because it's passed by reference the object is kept up to date between calls so we can mock each method to simulate a simple iterator. Now we can use the iterator mock as normal without having to worry about a rigid call order.

Sample method below that you can use to setup an iterator mock:

/**
 * Setup methods required to mock an iterator
 *
 * @param PHPUnit_Framework_MockObject_MockObject $iteratorMock The mock to attach the iterator methods to
 * @param array $items The mock data we're going to use with the iterator
 * @return PHPUnit_Framework_MockObject_MockObject The iterator mock
 */
public function mockIterator(PHPUnit_Framework_MockObject_MockObject $iteratorMock, array $items)
{
    $iteratorData = new \stdClass();
    $iteratorData->array = $items;
    $iteratorData->position = 0;

    $iteratorMock->expects($this->any())
                 ->method('rewind')
                 ->will(
                     $this->returnCallback(
                         function() use ($iteratorData) {
                             $iteratorData->position = 0;
                         }
                     )
                 );

    $iteratorMock->expects($this->any())
                 ->method('current')
                 ->will(
                     $this->returnCallback(
                         function() use ($iteratorData) {
                             return $iteratorData->array[$iteratorData->position];
                         }
                     )
                 );

    $iteratorMock->expects($this->any())
                 ->method('key')
                 ->will(
                     $this->returnCallback(
                         function() use ($iteratorData) {
                             return $iteratorData->position;
                         }
                     )
                 );

    $iteratorMock->expects($this->any())
                 ->method('next')
                 ->will(
                     $this->returnCallback(
                         function() use ($iteratorData) {
                             $iteratorData->position++;
                         }
                     )
                 );

    $iteratorMock->expects($this->any())
                 ->method('valid')
                 ->will(
                     $this->returnCallback(
                         function() use ($iteratorData) {
                             return isset($iteratorData->array[$iteratorData->position]);
                         }
                     )
                 );

    $iteratorMock->expects($this->any())
                 ->method('count')
                 ->will(
                     $this->returnCallback(
                         function() use ($iteratorData) {
                             return sizeof($iteratorData->array);
                         }
                     )
                 );

    return $iteratorMock;
}

If you just need to test against a generic iterator, then PHP (in the SPL extension - which can't be turned off in PHP > 5.3) has built in array wrappers that implement Iterable: SPL Iterators. e.g.

$mock_iterator = new \ArrayIterator($items);
$test_class->methodExpectingGenericIterator($mock_iterator);
$resulting_data = $mock_iterator->getArrayCopy();