前回はDDDでflutter testをする際の、全体の設計とテスト以外の実装について解説しました。(前回の記事)
今回は、DDDの中でも依存が多くて大変なview_model層におけるテストの実装方法を紹介します!(view_model層が実装できたらrepositoryも他の層でも問題なく実装できると思います)
お品書き
- 意識したこと 〜対応三昧〜
- 実装方法 〜考慮の積み重ね〜
- 最後に
意識したこと
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されたrepository
をinjectするためです。
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!