😽
[Riverpod]FutureProvider内でStateNotifierをwatchする際のテスト用モック作成のメモ
はじめに
モッククラスの作成が全然うまくいかなかったので、メモ。
※テストの知見深いわけではないので、おかしいところあればご指摘お願いします🙇♂️
↓こちらのリポジトリを使用
環境
- Flutter 2.8.1
- flutter_hooks 0.18.0
- hooks_riverpod 1.0.3
- mockito 5.0.17
やったこと
下記のメモ情報を取得してくる memoProvider
をテストしようとした。
final memoProvider =
FutureProvider.family.autoDispose<Memo?, String?>((ref, memoId) async {
// memoIdがnullの場合はnullを返却
if (memoId == null) {
return null;
}
// user情報をwatchしてidを取得
final user =
ref.watch(authControllerProvider.select((value) => value.firebaseUser));
// userIdとmemoIdからメモ情報を取得
return ref.read(memoRepositoryProvider).fetchMemo(
userId: user.value!.uid,
memoId: memoId,
);
});
authControllerの実装はこちら
final authControllerProvider = StateNotifierProvider<AuthController, AuthState>(
(ref) => AuthController._(FirebaseAuth.instance),
);
class AuthController extends StateNotifier<AuthState> {
AuthController._(
this._auth,
) : super(AuthState()) {
_auth.authStateChanges().listen(
(user) {
state = state.copyWith(
firebaseUser: AsyncValue.data(user),
);
},
);
}
final FirebaseAuth _auth;
Future<void> signOut() => _auth.signOut();
}
class AuthState with _$AuthState {
factory AuthState({
(AsyncValue<User?>.loading()) AsyncValue<User?> firebaseUser,
}) = _AuthState;
AuthState._();
late final bool hasAlreadySignedIn = firebaseUser.value != null;
}
上記の memoProvider
に対して、こんな感じのテストを組んでいた。(mockitパッケージを使用しています)
mockクラスの作成
下記クラスを flutter pub run build_runner build
でMockクラスを作成。
test/mocks/generate_mocks.dart
([
Memo,
MemoRepository,
User,
AuthState,
AuthController,
])
void main() {}
void main() {
group('[memoProvider]', () {
// memoIdがnullの時nullが返ってくるかテスト
// こっちはOK
group('when the memoId is set to null', () {
test('return null', () async {
const settingValue = null;
final container = ProviderContainer(overrides: [
memoRepositoryProvider.overrideWithValue(MockMemoRepository()),
]);
expect(
container.read(memoProvider(settingValue)),
const AsyncValue<Memo?>.loading(),
);
await container.read(memoProvider(settingValue).future);
expect(
container.read(memoProvider(settingValue)),
const AsyncData<Memo?>(null),
);
});
});
// memoIdとuserIdがセットされている時Memoオブジェクトが返却されるかテスト
// こっちはNG
group('when the memoId is set to "1" and the userId is set to "test"', () {
test('return Memo object', () async {
const settingValue = '1';
final result = MockMemo();
final mockMemoRepository = MockMemoRepository();
final mockMemoRepository = MockMemoRepository();
when(
mockMemoRepository.fetchMemo(
userId: anyNamed('userId'),
memoId: anyNamed('memoId'),
),
).thenAnswer((_) async => result);
final mockUser = MockUser();
when(mockUser.uid).thenReturn('test');
final mockAuthState = MockAuthState();
when(mockAuthState.firebaseUser).thenReturn(AsyncData(mockUser));
final mockAuthController = MockAuthController();
when(mockAuthController.state).thenReturn(mockAuthState);
final container = ProviderContainer(overrides: [
memoRepositoryProvider.overrideWithValue(mockMemoRepository),
authControllerProvider.overrideWithValue(mockAuthController),
]);
expect(
container.read(memoProvider(settingValue)),
const AsyncValue<Memo?>.loading(),
);
await container.read(memoProvider(settingValue).future);
expect(
container.read(memoProvider(settingValue)),
AsyncData<Memo?>(result),
);
});
});
});
}
しかし、このコードだと二つ目の group
のテストで下記のエラーでうまく動かない。
Thrown exception:
MissingStubError: 'addListener'
No stub was found which matches the arguments of this method call:
addListener(Closure: (AuthState) => void, {fireImmediately: true})
エラーが出ていたのが memoProvider
の下記の箇所。
final user =
ref.watch(authControllerProvider.select((value) => value.firebaseUser));
エラー内容から addListner
メソッドのstubがないと言われているので、whenで実装しようとしてもうまくいかない。
そのため、下記の StateNotifier
と AuthController
のモッククラスを作成。
test/mocks/mocks.dart
class MockStateNotifier<T> extends StateNotifier<T> with Mock {
MockStateNotifier(T state) : super(state);
}
class MockAuthController extends MockStateNotifier<AuthState>
implements AuthController {
MockAuthController(AuthState authState) : super(authState);
}
テストを下記のように書き換えると成功した。
group('when the memoId is set to "1" and the userId is set to "test"', () {
test('return Memo object', () async {
const settingValue = '1';
final result = MockMemo();
final mockMemoRepository = MockMemoRepository();
final mockMemoRepository = MockMemoRepository();
when(
mockMemoRepository.fetchMemo(
userId: anyNamed('userId'),
memoId: anyNamed('memoId'),
),
).thenAnswer((_) async => result);
final mockUser = MockUser();
when(mockUser.uid).thenReturn('test');
final mockAuthState = MockAuthState();
when(mockAuthState.firebaseUser).thenReturn(AsyncData(mockUser));
- final mockAuthController = MockAuthController();
- when(mockAuthController.state).thenReturn(mockAuthState);
+ final mockAuthController = MockAuthController(mockAuthState);
final container = ProviderContainer(overrides: [
memoRepositoryProvider.overrideWithValue(mockMemoRepository),
authControllerProvider.overrideWithValue(mockAuthController),
]);
expect(
container.read(memoProvider(settingValue)),
const AsyncValue<Memo?>.loading(),
);
await container.read(memoProvider(settingValue).future);
expect(
container.read(memoProvider(settingValue)),
AsyncData<Memo?>(result),
);
});
});
zennに一度投稿してみたくてメモ程度に投稿しました。
StateNotifierの中身のコードの理解ができていない。勉強せねば(泣)
Discussion