html-over-dns/content/posts/2015-01-24-multiple-return-values-with-mockery.md

105 lines
2.8 KiB
Markdown
Raw Normal View History

+++
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:
```php
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:
```php
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:
```php
$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:
```php
$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.
[Mockery]: https://github.com/mockery/mockery