Flutter TestとDDDの組み合わせで快適なFlutter Lifeを  〜view_modelを添えて〜

前回はDDDでflutter testをする際の、全体の設計とテスト以外の実装について解説しました。（[前回の記事]([https://qiita.com/nihon_kaizou/items/d72fa2d2ed6eaf21fccf](https://qiita.com/nihon_kaizou/items/d72fa2d2ed6eaf21fccf))）

今回は、DDDの中でも依存が多くて大変なview_model層におけるテストの実装方法を紹介します！（view_model層が実装できたらrepositoryも他の層でも問題なく実装できると思います）

# お品書き

1. 意識したこと　〜対応三昧〜
2. 実装方法　〜考慮の積み重ね〜
3. 最後に

# 意識したこと

## repositoryの対応

view_modelで使用するrepositoryは非同期対応のためにmock化されていると思いますが、それをtestに対応させるにはどのようにすれば良いでしょうか？

実は何も難しいことをする必要はありません。

では何をすれば良いのかというと、
### classをwrapして、mockをinjectする


それだけでいいんですよ！（なんか、ルー大柴みたいになった笑）

具体的な実装方法は次にお話しします。

:::note info
今回における**wrap**はclassにさらに何かしらの処理を挟んで同じclassを生成すること、**inject**はあるclassに対して依存を注入すること(e.x. repository -> view_modelに注入する感じ)と定義します。
:::

## Provider(Riverpod)の対応

これが一番面倒だと思います。

testでproviderを扱う際に重要な奴が存在します。それは、`ProviderContainer`です。

これは**stateが格納された格納されたオブジェクト**です([参照](https://pub.dev/documentation/riverpod/latest/riverpod/ProviderContainer-class.html))が、この説明だけではよくわからないと思います。。。
なので、直感的にわかるように説明すると、

**`ProviderScope`の代わりに`ProviderContainer`で初期化する！**

って感じで使ったもらえるといいと思います！笑

# 実装方法

今回使用する`model`, `repository`, `mock(repository)`, `view_model`を定義します。

```dart
// model
class User {
  User({
    required this.id,
    required this.name,
    required this.email,
  });

  final int id;
  final String name;
  final String email;
}

// repository
final userRepositoryProvider = Provider<UserRepository>(
  (ref) => UserRepository(),
);

class UserRepository {
  Future<User> createUser(int id) async {
    // do something
    return User(id: id, name: name, email: email);
  }
}

// mock repository
class MockUserRepository implements UserRepository {
  Future<User> createUser(int id) async {
    // do something
    return User(id: id, name: name, email: email);
  }
}

// view model
final userViewModelProvider = Provider<UserViewModel>(
  (ref) => UserViewModel(
    ref,
    ref.watch(userRepositoryProvider),
  ),
);

class UserViewModel {
  UserViewModel(
    this.ref,
    this.userRepository,
  );
  final UserRepository userRepository;
  final ProviderRef ref;

  Future<User> createUser(int id) async {
    // do something
    return User(id: id, name: email, email: email);
  }
}
```

これらを用いて、テストを行います。

`userViewModel`を使用するので最初に**wrap**した状態のclassを定義します。

**wrap**する理由は`repository`ではなく`Mockされたrepository`を**inject**するためです。

```dart
void main() {
	// wrapされたview_model(class)
  UserViewModel userViewModel(
    ProviderContainer container,
  ) {
		// Mockを定義
    final userRepository = MockUserRepository();

    return container.read(
	  // Mockを注入
      Provider((ref) => UserViewModel(ref, userRepository)),
    );
  }
```

次に、Provider(Riverpod)を使用する少し前に`ProviderContainer`を定義します。

どのタイミングでも問題はないですが、今回は、各testでproviderを使用する前提なので、Test Groupの初めに定義します。

```dart
void main() {
  ...

  group('Create User', () {
    // providerを使う際のオブジェクトを作成
    final container = ProviderContainer();
  });
}
```

ここまで準備ができたら、あとは各々のテストを書いていくだけです。

```dart
void main() {
  UserViewModel userViewModel(
    ProviderContainer container,
  ) {
    final userRepository = MockUserRepository();

    return container.read(
      Provider((ref) => UserViewModel(ref, userRepository)),
    );
  }

  group('Create User', () {
    // providerを使う際のオブジェクトを作成
    final container = ProviderContainer();

    test('When success', () async {
      final viewModel = userViewModel(container);
      final result = await viewModel.createUser(1);
      expect(result.id, 1);
    });

    test('When error', () async {
      final viewModel = userViewModel(container);
      await viewModel.createUser(0).catchError((e) {
        expect(e, isA<Exception>());
      });
    });
  });
}
```

# 最後に

前回に引き続き、DDDを用いたFlutter Testの設計＆実装方法について紹介しましたがいかがでしたか？
もっと具体例を用いて説明できたらよかったのですが、時間の都合上省略してしまいました🙇
また機会があったら解説したいと思います！

もっと、つよつよのFlutterエンジニアになれるように頑張るぞ！！！！！

皆さんも楽しいFlutter Lifeをお過ごしください！

Cheers!