Laravelで外部ストレージ等にアクセスする際、Storageを使ったりしますよね。
今回は、Storageで接続するサーバとのFTP接続に失敗したら
リトライする機能をテストしてみたいと思います。
環境
Windows 10
Laravel 6.x
リトライ機能の実装
↓こんな感じで組んでおきます。
class StorageRetrySample
{
public function retryMakeDirectory($path)
{
$isSuccess = false;
for ($cnt = 0; $cnt < config('const.retry.limit'); $cnt++) {
try {
Storage::makeDirectory($path);
$isSuccess = true;
break;
} catch (\Exception $e) {
Log::debug('retry ... ' . $cnt);
usleep('const.retry.sleep_time');
}
}
if (!$isSuccess) throw $e;
return 'success';
}
}
retry_limit
とsleep_time
はconst.phpで定義しておきましょう。
今回のサンプルでは以下の設定にします。
return [
'retry' => [
'limit' => 5,
'sleep_time' => 100000
]
]
テスト作成
今回の狙いは「接続に失敗した場合にリトライする」なので、
想定する例外はConnectionRuntimeException
です。
これが発生した時にリトライ出来ていればOK。
namespace Tests\Unit;
use League\Flysystem\ConnectionRuntimeException;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
use StorageRetrySample;
class StorageRetrySampleTest extends TestCase
// このテストはエラーが発生することを期待してます
public function testRetryMakeDirectoryGetException()
{
// 接続失敗のエラーを待つ
$this->expectException(ConnectionRuntimeException::class);
// makeDirectoryメソッドが呼ばれたら5回エラーを投げる
Storage::shouldReceive('makeDirectory')
->atMost()
->times(5)
->andThrow(new ConnectionRuntimeException('接続失敗'));
// makeDirectoryメソッドが呼ばれたら1回結果をそのまま返す
Storage::shouldReceive('makeDirectory')
->once()
->andReturnSelf();
// 実際に動かすと、↑で準備していたものが動き出す
StorageRetrySample::retryMakeDirectory('test');
}
// このテストはエラーが発生しないことを期待してます
public function testStorageMakeDirectoryRetrySafe()
{
// エラーが返ってくることを期待しないので、何も書かない
// 4回エラーを返す
Storage::shouldReceive('makeDirectory')
->atMost()
->times(4)
->andThrow(new ConnectionRuntimeException('接続失敗'));
// 5回目で正常リターン
Storage::shouldReceive('makeDirectory')
->once()
->andReturnSelf();
$result = StorageRetrySample::retryMakeDirectory('test');
$this->assertEquals('success', $result);
}
}
軽く説明
メソッド | 説明 |
shouldReceive(‘メソッド名’) | 指定したメソッドの返値を指定できる |
atMost() | 少なくとも |
once() | 一度だけ |
times(数値) | 繰り返す回数を指定できる |
andReturnSelf() | 本来のメソッドの返値を使う |
andThrow(Exceptionを指定) | 指定したExceptionを投げる |
躓きポイント
自分だけなのかもしれませんが、実際にこのテストを組もうと試行錯誤したポイントとしては
一度モックを定義すると、正常リターンが返ってこなくなるという点です。
shouldReceive()
を定義することで、モックが使われるようになります。
ずーっとエラーが発生するケースはこれだけで良いのですが、
5回までリトライするという機能なので、境界値テストとして
- 4回まで失敗して良い。
- 5回失敗したらエラー。
という2ケースは必ず必要となると思います。
なので、4回まで失敗させて、5回目で成功させる必要があるのですが、shouldReceive()->atMost()->times(4)
としても、
5回目までエラーが出てしまいました。
そこで、andReturnSelf()
の出番です。
これを指定すると、本来のメソッドの返値が復活するので、
エラーが発生しなくなります。
まとめ
如何でしたでしょうか
ネットでshouldReveive関連をググっても
andReturnSelf()を使う例があまり見られず、
単純にエラーを起こしてcatchできるか?
というサンプルが多かった気がします
今回のように、「n回までエラーOKのようなケース」は
多いと思うので、覚えておくと良いと思います。
ではまた!