Flutter TDD Clean Architecture Course その6 - Repositoryの実装

- [こちらのチュートリアル](https://resocoder.com/2019/09/19/flutter-tdd-clean-architecture-course-6-repository-implementation/)の解説をします。
- チュートリアルを読むのが早いと思いますが、[概要をまとめた](https://qiita.com/maria_mari/items/8fdd92af13a36c663360)のでこちらもご参照ください。


## 前回までの流れ
- CleanArchitectureの図の内側から外側に向かって以下のようにコーディングをしています
    - Entityの作成
    - UseCaseの作成
    - RepositoryのContractの作成
    - Modelの作成
    - DataSourcesのContractの作成
    - Repositoryの実装ファイルの作成（途中まで）

## 今回やること
- Repositoryの実装をするために、１つ外側のDataSourcesのContractを作成してあります。
    - 今回はこのDataSourcesのContractのMockを用いてRepositoryの実装をします

---

# getConcreteNumberTrivia
## NetworkInfoを呼ぶというテスト実装

```number_trivia_repository_impl_test.dart
group('getConcreteNumberTrivia', () {
  // DATA FOR THE MOCKS AND ASSERTIONS
  // We'll use these three variables throughout all the tests
  final tNumber = 1;
  final tNumberTriviaModel =
      NumberTriviaModel(number: tNumber, text: 'test trivia');
  final NumberTrivia tNumberTrivia = tNumberTriviaModel;

  test('should check if the device is online', () {
    //arrange
    when(mockNetworkInfo.isConnected).thenAnswer((_) async => true);
    // act
    repository.getConcreteNumberTrivia(tNumber);
    // assert
    verify(mockNetworkInfo.isConnected);
  });
});
```

```number_trivia_repository_impl.dart
@override
Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(int number) {
  networkInfo.isConnected;
  return null;
}
```

- この実装は何もしていませんが、NetworkInfoが呼ばれるというテストを通している
    - テストを通すコードのみを追加するというのがTDDの手法になる

## 主要なテスト
- 省略
    - [チュートリアル](https://resocoder.com/2019/09/19/flutter-tdd-clean-architecture-course-6-repository-implementation/)参照

## 例外発生時のテスト
```test.dart
test(
  'should return server failure when the call to remote data source is unsuccessful',
  () async {
    // arrange
    when(mockRemoteDataSource.getConcreteNumberTrivia(tNumber))
        .thenThrow(ServerException());
    // act
    final result = await repository.getConcreteNumberTrivia(tNumber);
    // assert
    verify(mockRemoteDataSource.getConcreteNumberTrivia(tNumber));
    verifyZeroInteractions(mockLocalDataSource);
    expect(result, equals(Left(ServerFailure())));
  },
);
```

```impl.dart
@override
Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(
  int number,
) async {
  networkInfo.isConnected;
  try {
    final remoteTrivia =
        await remoteDataSource.getConcreteNumberTrivia(number);
    localDataSource.cacheNumberTrivia(remoteTrivia);
    return Right(remoteTrivia);
  } on ServerException {
    return Left(ServerFailure());
  }
}
```

- DataSourceで例外を投げた場合に、RepositoryはcatchしてFailureというエラーを表すインスタンスを返す
    - Eitherを用いる

## getRandomNumberTrivia
- 省略
    - [チュートリアル](https://resocoder.com/2019/09/19/flutter-tdd-clean-architecture-course-6-repository-implementation/)参照


## 2つのユースケースでのリファクタリング
- `getConcreteNumberTrivia`と`getRandomNumberTrivia`を共通化する
    - 以下のような関数型エイリアアスを用いて、`remoteDataSource.getConcreteNumberTrivia(number)`と`remoteDataSource.getRandomNumberTrivia()`を引数でとれるようにする

```dart
typedef Future<NumberTrivia> _ConcreteOrRandomChooser();
```

```impl.dart
typedef Future<NumberTrivia> _ConcreteOrRandomChooser();

class NumberTriviaRepositoryImpl implements
...
@override
  Future<Either<Failure, NumberTrivia>> getConcreteNumberTrivia(
    int number,
  ) async {
    return await _getTrivia(() {
      return remoteDataSource.getConcreteNumberTrivia(number);
    });
  }

  @override
  Future<Either<Failure, NumberTrivia>> getRandomNumberTrivia() async {
    return await _getTrivia(() {
      return remoteDataSource.getRandomNumberTrivia();
    });
  }

  Future<Either<Failure, NumberTrivia>> _getTrivia(
    _ConcreteOrRandomChooser getConcreteOrRandom,
  ) async {
    if (await networkInfo.isConnected) {
      try {
        final remoteTrivia = await getConcreteOrRandom();
        localDataSource.cacheNumberTrivia(remoteTrivia);
        return Right(remoteTrivia);
      } on ServerException {
        return Left(ServerFailure());
      }
    } else {
      try {
        final localTrivia = await localDataSource.getLastNumberTrivia();
        return Right(localTrivia);
      } on CacheException {
        return Left(CacheFailure());
      }
    }
  }
}

```

# 学んだこと
## DataSourceは例外を投げるように設計する
- 前回まで強調されていたように、アプリケーションの外側との境界であるDataSourceは例外を投げる
    - 外部の以上は明示的に例外として投げる
        - 404 error
        - Cacheが存在しない
- Repositoryは例外をcatchしてエラーを返す
    - このチュートリアルでは`Either<Failure, NumberTrivia>`を用いる

## TDDでの実装はテストを通すことのみに集中する
- NetworkInfoが呼ばれているというテストを通す実装では、NetworkInfoを呼ぶというコードだけ書く。他のコードは書かない。
    - 他のコードはその部分のテストを通すために書く
        - テストが失敗していない状態で実装を書き足すと、TDDにはならないし、テストに抜け漏れが発生する


## 関数型エイリアス
- いずれ改めて記事を書きます。