2.8 KiB
		
	
	
	
	
	
	
	
			
		
		
	
	+++ title = Mockery: returning values and throwing exceptions date = 2015-04-24 author = Jacob Kiers +++
Last week, I had to write a piece of code that contains retry logic. Naturally, I want to test it. That proved trickier than expected.
The application code looks like this:
class Sender
{
    protected $connection;
    public function send()
    {
        $success = false;
        $i = 0;
        do {
            $i++;
            try {
                $success = $this->doSend($i);
            } catch (SenderException $e) {
                $success = false;
            }
        } while (!$success && $i < 3);
        return $success;
    }
    protected function doSend($data)
    {
        // Can throw SenderException
        $response = $this->connection->send($data);
        if ('OK' === $response) {
            return true;
        }
        return false;
    }
}
I specifically want to test the retry logic, so I have to mock the ::doSend() method. Then I can simulate the different outcomes (returning true or false, or throwing a SenderException).
I use Mockery to do the real work. It is a great library. If you don't know it yet, please check it out. I will wait right here...
Now, since ::doSend() is a protected method, Mockery must be instructed to allow that.
So after the first try, I ended up with:
public function testItWillRetrySending()
{
    $sender = M::mock('Sender');
    $sender->shouldAllowMockingProtectedMethods()
    $sender->shouldReceive('doSend')
        ->andReturn(false, new Exception());
}
To my surprise, this did not work. Instead of throwing the exception, Mockery returns it. So my next try was this:
$sender->shouldReceive('doSend')
    ->andReturn(false)
    ->andThrow(new Exception());
Another surprise: with this code, Mockery will always throw the exception, and ignore the first return value (false). After some debugging, I found out that Mockery just overwrites the return values in this case.
Fortunately, there is another way to return multiple return values: the ::andReturnUsing() method. It gives full control over the return values.
So I ended up with this testing code:
$return_value_generator = function () {
    static $counter = 0;
    $counter++;
    switch ($counter) {
        case 1: return false;
        case 2: throw new SenderException();
        case 3: return true;
        default: throw new Exception("Should never reach this."); 
    }
};
$sender = M::mock('Sender');
$sender->shouldAllowMockingProtectedMethods()
    ->shouldReceive('doSend')
    ->andReturnUsing($return_value_generator);
This works perfectly. It feels a bit like a hack though. So if you know a better way or have any other remarks, please let me know.