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のようなケース」は
多いと思うので、覚えておくと良いと思います。
ではまた!
 
 
