2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

プログラミング研究会Tech.UniAdvent Calendar 2022

Day 16

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

Posted at

前回はDDDでflutter testをする際の、全体の設計とテスト以外の実装について解説しました。(前回の記事

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

お品書き

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

意識したこと

repositoryの対応

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

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

では何をすれば良いのかというと、

classをwrapして、mockをinjectする

それだけでいいんですよ!(なんか、ルー大柴みたいになった笑)

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

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

Provider(Riverpod)の対応

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

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

これはstateが格納された格納されたオブジェクトです(参照)が、この説明だけではよくわからないと思います。。。
なので、直感的にわかるように説明すると、

ProviderScopeの代わりにProviderContainerで初期化する!

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

実装方法

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

// 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されたrepositoryinjectするためです。

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の初めに定義します。

void main() {
  ...

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

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

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!

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?