🦅
[Flutter][Riverpod] .autoDispose使用時のunit testにおける注意点: listen()を使用する
はじめに
.autoDispose
を使用すると、例えば画面遷移時にWidgetと共にそのWidgetが参照するクラス(例:ViewModel)のインスタンスも同時に破棄でき、再度画面を開いた時に初期状態で表示するということが可能になります。AndroidのViewModelに近い挙動です。
注意点
.autoDispose
による状態破棄タイミングは、状態を購読するオブジェクトが存在しなくなった時です。これにより状態を購読しているWidgetが破棄されると、その状態(eg: ViewModel)も破棄されます。
つまりtestにおいても明示的にテスト対象クラスの状態を購読しておかないと、即座に対象クラスがdisposeされてしまいテストが失敗します。
ChangeNotifier
を継承したViewModelをunit testする例で考えます。ViewModelが以下のような形で.autoDispose
を使用してDIされるとします。
final viewModelProvider = ChangeNotifierProvider.autoDispose(
(ref) => viewModel(repository: ref.read(repositoryProvider)));
例: ProviderContainer.read
では購読にならないため即座にdisposeされる
test('ViewModel test', () async {
final repository = MockRepository();
when(repository.getData(any)).thenAnswer((_) async => data);
final container = ProviderContainer(
overrides: [repositoryProvider.overrideWithValue(repository)],
);
final viewModel = container.read(viewModelProvider); // 👈
await viewModel.fetchData();
expect(viewModel.data, data);
});
以下のようなエラーとなりテストが失敗するはずです。
A ViewModel was used after being disposed.
Once you have called dispose() on a ViewModel, it can no longer be used.
ただし、.autoDispose
を使用していない場合は上記でも動作します。
解決策
以下のようにProviderContainer.listen
を呼び、そこからViewModelを取得してテストを行います。
これによりテスト終了までViewModelを破棄されることなく動作させることができます。
test('ViewModel test', () async {
final repository = MockRepository();
when(repository.getData(any)).thenAnswer((_) async => data);
final container = ProviderContainer(
overrides: [repositoryProvider.overrideWithValue(repository)],
);
final viewModel = container.listen(viewModelProvider).read(); // 👈
await viewModel.fetchData();
expect(viewModel.data, data);
});
以上です!
Discussion