- こちらのチュートリアルの解説をします。
- チュートリアルを読むのが早いと思いますが、概要をまとめたのでこちらもご参照ください。
前回までの流れ
- CleanArchitectureの図の内側から外側に向かって以下のようにコーディングをしています
- Entityの作成
- UseCaseの作成
- RepositoryのContractの作成
- Modelの作成
- DataSourcesのContractの作成
- Repositoryの実装ファイルの作成(途中まで)
今回やること
- Repositoryの実装をするために、1つ外側の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の手法になる
主要なテスト
- 省略
- チュートリアル参照
例外発生時のテスト
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
- 省略
- チュートリアル参照
2つのユースケースでのリファクタリング
-
getConcreteNumberTrivia
とgetRandomNumberTrivia
を共通化する- 以下のような関数型エイリアアスを用いて、
remoteDataSource.getConcreteNumberTrivia(number)
とremoteDataSource.getRandomNumberTrivia()
を引数でとれるようにする
- 以下のような関数型エイリアアスを用いて、
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にはならないし、テストに抜け漏れが発生する
- 他のコードはその部分のテストを通すために書く
関数型エイリアス
- いずれ改めて記事を書きます。