FlutterでClean ArchitectureとTDDをやってみる その3 - Domain層のリファクタリング

- [こちらのチュートリアル](https://resocoder.com/2019/09/02/flutter-tdd-clean-architecture-course-3-domain-layer-refactoring/)の解説をします。
- コードは該当ページのGitHubリンクから取得するのが良いと思います。
- チュートリアルを読むのが早いと思いますが、[概要をこちらにまとめた](https://qiita.com/maria_mari/items/8fdd92af13a36c663360)のでこちらもご参照ください。


---

# call()メソッド
- call()を定義したクラスについて、`object.call()`を`object()`とも書ける
    - UseCaseを使う際に便利

```get_concrete_number_trivia_test.dart
// before
// final result = await usecase.execute(number: tNumber);

// after
final result = await usecase(number: tNumber);
```

# 2つめのUseCase`GetRandomNumberTrivia`を追加する
## UseCaseのBaseClass

- cleanなコーディングのためにはBaseClassを作るとよい
- UseCaseはcallメソッドを作るとわかりやすい
    - method名を覚えなくて良いため
- 以下のようなBase Classを用いる
    - call()にパラメータを使わない場合には`NoParams`を用いる
        - getRandomNumberTriviaの場合、パラメータを用いないため

```usecase.dart
import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart';

import '../error/failure.dart';

// method定義
// Type, ParamsはGenericsである。成功の場合の型をType、受け取るパラメータの型をParamsとして記述している
abstract class UseCase<Type, Params> {
  Future<Either<Failure, Type>> call(Params params);
}

// parameterを用いない場合に使うダミーのparameter
class NoParams extends Equatable {}
```

## BaseClassの継承
- `GetConcreteNumberTrivia`にBaseClassをextendsさせる
- int numberを渡せる`Params`クラスを作成する
    - これはintのまま渡してもよい。複数のパラメータを渡す場合はこのクラスを使うのが良いと思う

```get_concrete_number_trivia.dart
class GetConcreteNumberTrivia extends UseCase<NumberTrivia, Params> {
  ...
}

class Params extends Equatable {
  final int number;

  Params({@required this.number}) : super([number]);
}
```
- testをリファクタリングする
    - executeを省略する
    - Paramsを使用する
    - 実装が対応していないため、このテストは失敗する。

```get_concrete_number_trivia_test.dart
...
test(
  'should get trivia for the number from the repository',
  () async {
    // arrange
    when(mockNumberTriviaRepository.getConcreteNumberTrivia(any))
        .thenAnswer((_) async => Right(tNumberTrivia));
    // act
    final result = await usecase(Params(number: tNumber));
    // assert
    expect(result, Right(tNumberTrivia));
    verify(mockNumberTriviaRepository.getConcreteNumberTrivia(tNumber));
    verifyNoMoreInteractions(mockNumberTriviaRepository);
  },
);
```

- 実装を修正する

```get_concrete_number_trivia.dart
class GetConcreteNumberTrivia extends UseCase<NumberTrivia, Params> {
  ...
  @override
  Future<Either<Failure, NumberTrivia>> call(Params params) async {
    return await repository.getConcreteNumberTrivia(params.number);
  }
}
...
```

## GetRandomNumberTrivia

```get_random_number_trivia_test.dart
import 'package:clean_architecture_tdd_prep/core/usecase/usecase.dart';
import 'package:clean_architecture_tdd_prep/features/number_trivia/domain/entities/number_trivia.dart';
import 'package:clean_architecture_tdd_prep/features/number_trivia/domain/repositories/number_trivia_repository.dart';
import 'package:clean_architecture_tdd_prep/features/number_trivia/domain/usecases/get_random_number_trivia.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';

class MockNumberTriviaRepository extends Mock
    implements NumberTriviaRepository {}

void main() {
  GetRandomNumberTrivia usecase;
  MockNumberTriviaRepository mockNumberTriviaRepository;

  setUp(() {
    mockNumberTriviaRepository = MockNumberTriviaRepository();
    usecase = GetRandomNumberTrivia(mockNumberTriviaRepository);
  });

  final tNumberTrivia = NumberTrivia(number: 1, text: 'test');

  test(
    'should get trivia from the repository',
    () async {
      // arrange
      when(mockNumberTriviaRepository.getRandomNumberTrivia())
          .thenAnswer((_) async => Right(tNumberTrivia));
      // act
      // Since random number doesn't require any parameters, we pass in NoParams.
      final result = await usecase(NoParams());
      // assert
      expect(result, Right(tNumberTrivia));
      verify(mockNumberTriviaRepository.getRandomNumberTrivia());
      verifyNoMoreInteractions(mockNumberTriviaRepository);
    },
  );
}
```
- 実装する

```get_random_number_trivia.dart
import 'package:dartz/dartz.dart';

import '../../../../core/error/failure.dart';
import '../../../../core/usecase/usecase.dart';
import '../entities/number_trivia.dart';
import '../repositories/number_trivia_repository.dart';

class GetRandomNumberTrivia extends UseCase<NumberTrivia, NoParams> {
  final NumberTriviaRepository repository;

  GetRandomNumberTrivia(this.repository);

  @override
  Future<Either<Failure, NumberTrivia>> call(NoParams params) async {
    return await repository.getRandomNumberTrivia();
  }
}
```

# 学んだこと
- UseCaseが複数ある場合はインターフェイスを作るときれいに設計できる
- call()メソッドを使うことでインスタンスを関数化できるのが便利
- 複数UseCaseで異なる引数をとる場合、Genericsを使う
    - 引数がない場合のダミーとして `NoParams` を使うのがよい
    - NoParamsはインターフェイスのファイルに記述しておけば、UseCaseの実装から呼べる
    - `GetConcreteNumberTrivia`用の`Params`は同ファイルに記述すれば、同UseCaseを呼ぶファイルで同じく呼べる

- **Testを次の3段階に記述するのは覚えておくと何をするか迷わなくてよい**
    - **arrange**: mockの作成、DI等の準備、mockの挙動設定
    - **act**: テスト対象メソッドを呼ぶ
    - **assert**: 検証する