ezzatron
ezzatron

Reputation: 817

PHPUnit mocking - fail immediately when method called x times

With PHPUnit, I am testing a sequence of method calls using ->at(), like so:

$mock->expects($this->at(0))->method('execute')->will($this->returnValue('foo'));
$mock->expects($this->at(1))->method('execute')->will($this->returnValue('bar'));
$mock->expects($this->at(2))->method('execute')->will($this->returnValue('baz'));

How can I set up the mock so that, in the above scenario, if execute() is called four or more times, it will immediately fail? I tried this:

$mock->expects($this->at(3))->method('execute')->will($this->throwException(new Exception('Called too many times.')));

But this also fails if execute() is not called four times. It needs to fail immediately, otherwise the system under test will produce errors of its own, which causes the resulting error message to be unclear.

Upvotes: 5

Views: 3180

Answers (4)

ezzatron
ezzatron

Reputation: 817

I managed to find a solution in the end. I used a comination of $this->returnCallback() and passing the PHPUnit matcher to keep track of the invocation count. You can then throw a PHPUnit exception so that you get nice output too:

$matcher = $this->any();
$mock
    ->expects($matcher)
    ->method('execute')
    ->will($this->returnCallback(function() use($matcher) {
        switch ($matcher->getInvocationCount())
        {
            case 0: return 'foo';
            case 1: return 'bar';
            case 2: return 'baz';
        }

        throw new PHPUnit_Framework_ExpectationFailedException('Called too many times.');
    }))
;

Upvotes: 11

Gary
Gary

Reputation: 1525

For special cases like this, I typically use something like the following:

public function myMockCallback() {
     ++$this -> _myCounter;
     if( $this -> _myCounter > 3 ) {
          // THROW EXCEPTION OR TRIGGER ERROR
     }
     ... THEN YOUR CASE STATEMENT OR IF/ELSE WITH YOUR CHOICE OF RETURN VALUES
} 

... INSIDE TEST FUNCTION ....

$mockObject ->expects($this->any())
            ->method('myMethod')
            ->will($this->returnCallback( array ($this, 'myMockCallback' )));

Upvotes: 3

Shaked KO
Shaked KO

Reputation: 419

What about using data providers?

class MyTest extends PHPUnit.... { 
    /** 
     * @var const how much till throwing exception 
    */
    const MAX_EXECUTE_TILL_EXCEPTION = 3; 

    public function setUp(){} 

    public function tearDown(){} 

    /**
     *  @dataProvider MyExecuteProvider  
    */
    pulbic function testMyExecuteReturnFalse($data){
        $mock = //setup your mock here

        //if using "$ret" doesn't work you cant just call another private helper that will decide if you need to 
        //  return value or throwing exception 
        if (self::MAX_EXECUTE_TILL_EXCEPTION == $data){ 
            $ret = $this->throwException(new Exception('Called too many times.'));
        } else {
            $ret = $this->returnValue('foo'); 
        } 
        $mock->expects($this->at($data))->method('execute')->will($ret);
    }

    public function MyExecuteProvider(){
            return array(
            0,1,2,3
        )
    }
}

This is just another idea, and I think that zerkms suggested very good idea as well

Upvotes: 0

zerkms
zerkms

Reputation: 254886

You could separate test to 2 dependent methods, using @depends annotation.

In this case your first test only tests that there are exact 3 method executions, and second - other logic.

Upvotes: 2

Related Questions