Mocking Iterator with PHPUnit

When writing unit tests, it is important that you isolate the system under test from any dependencies. Put simply, this means you have to mock any other objects that your system under test interacts with. This can be tricky when a dependency implements the Iterator interface as you have to carefully mock all calls to five methods in the correct order.

This blog post provides an example test showing how to mock an Iterator in PHPUnit as well as a helper class to make this process straightforward. You can view the source code for the helper on PasteBin or download the entire source code as a gzipped TAR.

Simple Iterator class

To demonstrate, consider the following simple iterator class.

/**
 * Example list class
 *
 * @author Dave Gardner <dave@davegardner.me.uk>
 */
class exampleList implements Iterator
{
    /**
     * Our items
     *
     * @var array
     */
    private $items = array(
        'item1' => 'This is the first item',
        'item2' => 'This is the second item',
        'item3' => 'This is the third item',
        'item4' => 'This is the fourth item',
        'item5' => 'This is the fifth item'
    );

    /**
     * Get key of current item as string
     *
     * @return string
     */
    public function key()
    {
        echo "\033[36m" . __METHOD__ . "\033[0m\n";
        return key($this->items);
    }

    /**
     * Test if current item valid
     *
     * @return boolean
     */
    public function valid()
    {
        echo "\033[36m" . __METHOD__ . "\033[0m\n";
        return current($this->items) === FALSE
                ? FALSE
                : TRUE;
    }

    /**
     * Fetch current value
     *
     * @return string
     */
    public function current()
    {
        echo "\033[36m" . __METHOD__ . "\033[0m\n";
        return current($this->items);
    }

    /**
     * Go to next item
     */
    public function next()
    {
        echo "\033[36m" . __METHOD__ . "\033[0m\n";
        next($this->items);
    }

    /**
     * Rewind to start
     */
    public function rewind()
    {
        echo "\033[36m" . __METHOD__ . "\033[0m\n";
        reset($this->items);
    }
}

Ignore the strange bash escape codes (I can't help myself when writing CLI scripts). Instantiated objects of this class will loop through their five internal items (when asked) and echo out the methods being called in cyan! We can see this in action via the following code:

echo "\n\033[44;37;01mTest 1: foreach key => value\033[0m\n\n";

$list = new exampleList();
foreach ($list as $key => $value)
{
    echo "\033[01m$key = $value\033[0m\n";
}

echo "\n\033[44;37;01mTest 2: foreach value\033[0m\n\n";

$list = new exampleList();
foreach ($list as $value)
{
    echo "\033[01m$value\033[0m\n";
}

When we run this, we get the following.

Running the iterator

Mocking an Iterator for testing

This shows us exactly which methods are called and in what order. We can now use this to write some tests for a mocked iterator. All we have to do with our mocked object is set up PHPUnit expectations; the key point being the use of the at() matcher to specify the exact sequence of calls. The following test applies this to allow us to iterate through three mocked items. You can view the full source on PasteBin.

    public function testWhenMockThreeIterationWithNoKey()
    {
        $list = $this->buildSystemUnderTest();

        $list->expects($this->at(0))
             ->method('rewind');

        // iteration 1
        $list->expects($this->at(1))
             ->method('valid')
             ->will($this->returnValue(TRUE));
        $list->expects($this->at(2))
             ->method('current')
             ->will($this->returnValue('This is the first item'));
        $list->expects($this->at(3))
             ->method('next');

        // iteration 2
        $list->expects($this->at(4))
             ->method('valid')
             ->will($this->returnValue(TRUE));
        $list->expects($this->at(5))
             ->method('current')
             ->will($this->returnValue('This is the second item'));
        $list->expects($this->at(6))
             ->method('next');

        // iteration 2
        $list->expects($this->at(7))
             ->method('valid')
             ->will($this->returnValue(TRUE));
        $list->expects($this->at(8))
             ->method('current')
             ->will($this->returnValue('And the final item'));
        $list->expects($this->at(9))
             ->method('next');

        $list->expects($this->at(10))
             ->method('valid')
             ->will($this->returnValue(FALSE));

        $counter = 0;
        $values = array();
        foreach ($list as $value)
        {
            $values[] = $value;
            $counter++;
        }
        $this->assertEquals(3, $counter);

        $expectedValues = array(
            'This is the first item',
            'This is the second item',
            'And the final item'
        );
        $this->assertEquals($expectedValues, $values);
    }

Making this process simple via a helper

There a bunch of other tests within the full source code; I’ve left them out here to spare you a huge code block! This is actually a win. We have mocked an iterator and it works as expected. The only downside is that there’s a lot of stuff to repeat each time. To avoid this we can simply make a helper method to do the job for us. You can view the source code for this on PasteBin.

    /**
     * Mock iterator
     *
     * This attaches all the required expectations in the right order so that
     * our iterator will act like an iterator!
     *
     * @param Iterator $iterator The iterator object; this is what we attach
     *      all the expectations to
     * @param array An array of items that we will mock up, we will use the
     *      keys (if needed) and values of this array to return
     * @param boolean $includeCallsToKey Whether we want to mock up the calls
     *      to "key"; only needed if you are doing foreach ($foo as $k => $v)
     *      as opposed to foreach ($foo as $v)
     */
    private function mockIterator(
            Iterator $iterator,
            array $items,
            $includeCallsToKey = FALSE
            )
    {
        $iterator->expects($this->at(0))
                 ->method('rewind');
        $counter = 1;
        foreach ($items as $k => $v)
        {
            $iterator->expects($this->at($counter++))
                     ->method('valid')
                     ->will($this->returnValue(TRUE));
            $iterator->expects($this->at($counter++))
                     ->method('current')
                     ->will($this->returnValue($v));
            if ($includeCallsToKey)
            {
                $iterator->expects($this->at($counter++))
                         ->method('key')
                         ->will($this->returnValue($k));
            }
            $iterator->expects($this->at($counter++))
                     ->method('next');
        }
        $iterator->expects($this->at($counter))
                 ->method('valid')
                 ->will($this->returnValue(FALSE));
    }

Now we can repeat our test using the more succinct:

    public function testWhenMockThreeIterationWithNoKey()
    {
        $list = $this->buildSystemUnderTest();

        $expectedValues = array(
            'This is the first item',
            'This is the second item',
            'And the final item'
        );
        $this->mockIterator($list, $expectedValues);

        $counter = 0;
        $values = array();
        foreach ($list as $value)
        {
            $values[] = $value;
            $counter++;
        }
        $this->assertEquals(3, $counter);

        $this->assertEquals($expectedValues, $values);
    }

Tags: , , ,

4 Responses to “Mocking Iterator with PHPUnit”

  1. PHPUnit, mock di oggetti Iterator…

    Scrivendo unit test è molto comune avere a che fare con oggetti mock. Dovendo isolare il componente sotto test, ogni altro oggetto in gioco dovrebbe essere mockato. Questo permette di avere il necessario isolamento del componente sotto test, dal resto …

  2. Glen Scott says:

    Thanks for this mockIterator method, Dave. I’ve just re-used it in one of my own tests and it works well.

  3. Hey Dave,
    sounds interesting but for me it is too much code doing to less stuff, for me an array to iterator method would be just fine …

  4. amkoroew says:

    For me the following code was sufficient for testing my code. Maybe it’ll be useful for some search engine users, even after such a long time.
    $list->expects($this->any())
    ->method(’valid’)
    ->will($this->onConsecutiveCalls(TRUE, TRUE, TRUE, FALSE));
    $list->expects($this->any())
    ->method(’current’)
    ->will($this->onConsecutiveCalls($expectedValues[0], $expectedValues[1], $expectedValues[2]));

Leave a Reply