Search code examples
mockingphpunitexpectations

PHPUnit: Expectations orders ignored in test when same stubbed method called multiple times with different arguments


I have a method (let's call it method2) that calls another method (let's call it method1) multiple times but with different arguments.

Here is the class, MyClass.php:

<?php

class MyClass
{

    public function method1($arg)
    {
        return 'arg was ' . $arg;
    }

    public function method2()
    {
        // Calling the first time with 'one'
        $this->method1('one');

        // Calling other functions
        // ...

        // Calling the first time with 'two'
        $this->method1('two');
    }
}

When testing, I create a stub for method1 in order to control how / when it was called and what it returned. In my test for method2, I follow the order with which the code is executed inside method2.

Here is the test class, MyClassTest.php:

<?php

require_once 'MyClass.php';

class MyClassTest extends PHPUnit_Framework_TestCase
{

    /** @test */
    public function method2_was_called_successfully_with_one_and_then_two()
    {
        $myClassMock = $this->getMockBuilder('MyClass')
                            ->setMethods(['method1'])
                            ->getMock();

        $myClassMock->expects($this->once())
                    ->method('method1')
                    ->with($this->stringContains('one', true))
                    ->will($this->returnValue('arg was one'));

        // Testing code for the code between the two calls
        // ...

        $myClassMock->expects($this->once())
                    ->method('method1')
                    ->with($this->stringContains('two', true))
                    ->will($this->returnValue('arg was two'));

        $myClassMock->method2();
    }
}

In my test, it looks like PHPUnit does not follow this order and gets stuck with the last (second in this case) call of method1:

There was 1 failure:

1) MyClassTest::method2_was_called_successfully_with_one_and_then_two Expectation failed for method name is equal to when invoked 1 time(s) Parameter 0 for invocation MyClass::method1('one') does not match expected value. Failed asserting that 'one' contains "two".

/path/to/the/files/MyClass.php:14 /path/to/the/files/MyClassTest.php:28

FAILURES! Tests: 1, Assertions: 0, Failures: 1.

Any idea on what the basic thing is that I am missing / doing wrong here?


Solution

  • You must use at() instead of once() when configuring the mock:

        $myClassMock = $this->getMockBuilder('MyClass')
                            ->setMethods(['method1'])
                            ->getMock();
    
        $myClassMock->expects($this->at(0))
                    ->method('method1')
                    ->with($this->stringContains('one', true))
                    ->will($this->returnValue('arg was one'));
    
        $myClassMock->expects($this->at(1))
                    ->method('method1')
                    ->with($this->stringContains('two', true))
                    ->will($this->returnValue('arg was two'));
    
    
        // Testing code
        // ....
        // ....
    

    On a side note, it looks weird to me configuring the mock after some testing code is already executed. The usual pattern is configure all the calls that mocks should receive at the start of the test. Then exercise the SUT and check that all the calls were made (normally this last step is automatic).