Set mock expectations for multiple calls to a function

You want to:

Set mock expectations for multiple calls to a function.

Solution:

Use expects($this->at()) with a zero-based call index, along with with() and will() as usual.

Note that the index is the sequence of calls for the entire object, not just the method in question. So, if you are setting expectations for other method calls interleaved with the method in question, you have to take the ordering of those other calls into account when deciding the indices to use for these assertions.

As a result, while using this method can be useful, it also ties your tests to the exact order of the method calls on your object and breaks tests unnecessarily if code is rearranged in a functionally transparent way.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<?php
 
require_once "PHPUnit/Framework.php";
 
class FilesMultiplier
{
    /*
     * Read pairs of numbers from a file, mutiply, and output to
     * another file.
     */
    public function multiply($inputFile, $outputFile)
    {
        $line = $inputFile->read();
        while($line !== NULL) {
            $parts = explode(" ", $line);
            $a = (integer) $parts[0];
            $b = (integer) $parts[1];
            $result = ($a * $b);
            $outputFile->write($result);
 
            $line = $inputFile->read();
        }
    }
}
 
class TestFilesMultiplier extends PHPUnit_Framework_TestCase
{
    public function test_multiply()
    {
        $multiplier = new FilesMultiplier();
        /*
         * We pretend that FileObject is defined somewhere
         * else in our project.  Note that PHPUnit will mock
         * out any given name, even if it does not have a class
         * to base the mock on.
         */
        $inputFile = $this->getMock('FileObject',
                                    array('read'));
        /*
         * To assert across several calls to a function,
         * use $this->at() with a zero-based index.
         */
        $inputFile->expects($this->at(0))
                  ->method('read')
                  ->will($this->returnValue("3 4"));
        $inputFile->expects($this->at(1))
                  ->method('read')
                  ->will($this->returnValue("4 6"));
        $inputFile->expects($this->at(2))
                  ->method('read')
                  ->will($this->returnValue(NULL));
        /*
         * Be sure to put this count expectation AFTER any index
         * expectations.  If you put it before, the test will fail.
         */
        $inputFile->expects($this->exactly(3))
                  ->method('read');
        /*
         * Another issue with PHPUnit... you can set an expectation
         * on a misspelled function not previously mocked out.
         * So, this works.
         */
        $inputFile->expects($this->any())
                  ->method('red');
        $inputFile->expects($this->never())
                  ->method('red');
 
        $outputFile = $this->getMock('FileObject',
                                     array('write'));
        /* Interestingly, this works here. */
        $outputFile->expects($this->exactly(2))
                   ->method('write');
        /*
         * Note the difference in type here.
         * with() uses $this->isEqual() (i.e. ==) by default
         */
        $outputFile->expects($this->at(0))
                   ->method('write')
                   ->with("12");
        /*
         * If you want identical (i.e. ===), you can specify it.
         */
        $outputFile->expects($this->at(1))
                   ->method('write')
                   ->with($this->identicalTo(24));
 
        $multiplier->multiply($inputFile, $outputFile);
    }
}
 
?>