105 lines
2.8 KiB
Markdown
105 lines
2.8 KiB
Markdown
|
+++
|
||
|
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
|