Flutter TDD Clean Architecture Course その4 - Data Layer

- [こちらのチュートリアル](https://resocoder.com/2019/09/09/flutter-tdd-clean-architecture-course-4-data-layer-overview-models/)の雑なまとめをします。
- 完成コードはチュートリアルにあるのでご参照ください。
- チュートリアルを読むのが早いと思いますが、[概要をこちらにまとめた](https://qiita.com/maria_mari/items/8fdd92af13a36c663360)のでこちらもご参照ください。

---

# 全体の再把握
![CleanArchitecture.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779879/493855e6-86aa-8a6d-b6fc-e8188d23290a.jpeg)

- Clean Architectureは図の外側が内側に依存する
    - Domain層は独立して、何にも依存しない
- コーディングも独立した内側から書き始める
    - Entityがない状態だと、Entityを返すUseCaseを作成できない
- ただし、UseCaseはより外側のRepositoryを使用する
    - これを解決するためにContract（インターフェイス）を用いる
        - UseCaseはRepositoryのインターフェイスの依存するが実装には依存しない
        - **UseCaseはRepositoryの実装がない状態で、Repositoryのインターフェイスを継承したRepositoryのmockを使うことでTDDできる**

# Data Layer
![data-layer-diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779879/4ea60dac-6aa1-3e76-3a59-b65c87f7af7b.png)

## Data LayerはModelを扱う
- DataSourceはRepositoryが返す型は似ているが、以下の点で異なる
    - RepositoryはFailureとしてエラーをインラインで渡したが、DataSourceはExceptionsを投げる
    - RepositoryはNumberTrivia Entiｔｙを返したが、DataSourceはNumberTrivia Modelを返す
        - ModelはEntityにJSONを扱うメソッドを追加したものになる

## ModelをTDDする
- ModelがEntityの子クラスであるテスト

```number_trivia_model_test.dart
expect(tNumberTriviaModel, isA<NumberTrivia>());
```

### ModelのJSONを扱うメソッドのTDD
- fixtureを使う
    - testのデータを作るもの

#### Fixture
```trivia.json
{
 "text": "418 is the error code for \"I'm a teapot\" in the Hyper Text Coffee Pot Control Protocol.",
 "number": 418,
 "found": true,
 "type": "trivia"
}
```

```trivia_double.json
{
  "text": "Test Text",
  "number": 1.0,
  "found": true,
  "type": "trivia"
}
```

#### fixture_reader
- testファイルでfixtureを読み込むために、fixture_readerを用いる
```fixture_reader.dart
import 'dart:io';

String fixture(String name) => File('test/fixtures/$name').readAsStringSync();
```

#### fromJson
- 整数fixtureと少数fixtureを呼ぶ場合のテストを書き、テストが成功するように実装する
    - 整数完成後、少数のテスト失敗→実装修正を書く

```number_trivia_model_test.dart
void main() {
  final tNumberTriviaModel = NumberTriviaModel(number: 1, text: 'Test Text');
  ...
  group('fromJson', () {
    test('should return a valid model when the JSON number is an integer', () async {
      // arrange
      final Map<String, dynamic> jsonMap = json.decode(fixture('trivia.json'));
      // act
      final result = NumberTriviaModel.fromJson(jsonMap);
      // assert
      expect(result, tNumberTriviaModel);
    });
    test(
      'should return a valid model when the JSON number is regarded as a double',
      () async {
        // arrange
        final Map<String, dynamic> jsonMap = json.decode(fixture('trivia_double.json'));
        // act
        final result = NumberTriviaModel.fromJson(jsonMap);
        // assert
        expect(result, tNumberTriviaModel);
      },
    );
  });
}}
```

```number_trivia_model.dart
class NumberTriviaModel extends NumberTrivia {
  ...
  factory NumberTriviaModel.fromJson(Map<String, dynamic> json) {
    return NumberTriviaModel(
      text: json['text'],
      number: (json['number'] as num).toInt(),
    );
  }
}
```

#### toJson
- fromJsonと同様

```number_trivia_model_test.dart
...
final tNumberTriviaModel = NumberTriviaModel(number: 1, text: 'Test Text');
...
group('toJson', () {
  test(
    'should return a JSON map containing the proper data',
    () async {
      // act
      final result = tNumberTriviaModel.toJson();
      // assert
      final expectedJsonMap = {
        "text": "Test Text",
        "number": 1,
      };
      expect(result, expectedJsonMap);
    },
  );
});
...
```

```number_trivia_model.dart
class NumberTriviaModel extends NumberTrivia {
  ...
  Map<String, dynamic> toJson() {
    return {
      'text': text,
      'number': number,
    };
  }
}
```

# 学んだこと
## DataSourceとRepositoryの返す型は似ているが大きく異る
- **DataSourceが返す型とRepositoryが返す型は似ているが大きく異なる**ことを本チュートリアルは強調する
    - DataSourceが返す型はModelであり、Repositoryが返す型はEntityである。
        - 最も内側のEntityと、外側にあるModelはコードとふるまいは似ているが、CleanArchitecture上は別物として扱っている
            - コード上異なるのはJSONが扱えるところだけである
            - これは普遍的なビジネスロジックではなく外部のAPIやフレームワークに依存するものである

    - DataSourceとRepositoryが返すエラーも異なる
        - DataSourceは例外を投げる
        - RepositoryはFailureを使うことでインラインでエラーを返さない
- DataSourceがデータを受け取るまではコントロールできない外部の世界であり、データを受け取ってからはコントロールできる内部の世界であるため、この違いが生じる。DataSourceはその境界に位置する。

## fromJson()とtoJson()
### fromJson()
- fromJson()はMapを受け取りModelを生成するため、factoryとなる

### toJson()
- toJson()は引数を受け取らず、自身のプロパティからMapを生成するインスタンスメソッドとなる