Riverpod read方法测试:Widget测试结束时仍处于loading状态
问题:Riverpod使用
read方法时Mock Provider始终处于Loading状态 原AsyncProvider实现
@riverpod class NewUser extends _$NewUser { @override Future<UserModel?> build() { return getUser(); } Future<UserModel?> getUser() async { ...return user; } }
Mock Provider实现
class MockNewUserProvider extends AutoDisposeAsyncNotifier<UserModel?> with Mock implements NewUser { @override Future<UserModel?> build() { return getUser(); } @override Future<UserModel?> getUser() async { return users.first; } @override AsyncValue<UserModel> get state { return AsyncData(userInfo.first); } }
待测试Widget代码
在build方法中用read获取状态:
@override Widget build(BuildContext context, WidgetRef ref) { var userState = ref.read(newUserProvider);
后续使用该状态时,始终进入loading分支:
const Divider(thickness: 2), Row( children: [ userState.when( // 始终处于loading状态 data: (user) {
测试代码
void main() { late MockNewUserProvider mockMockNewUserProvider; setUp(() { mockMockNewUserProvider = MockNewUserProvider(); }); testWidgets('My widget loaded correctly', (tester) async { await tester.runAsync(() async { await tester.pumpApp( MyWidget(), overrides: [ newUserProvider.overrideWith(() => mockMockNewUserProvider) ]); await tester.pumpAndSettle();
调试确认Mock Provider本身正常,但read获取的状态一直是loading,换成watch就正常。同时测试时状态还没更新,测试就结束了。
解决办法
核心原因
ref.read只会获取调用瞬间的状态:AsyncNotifier初始化时默认状态是AsyncLoading,read不会监听后续状态变化,所以即使Provider后续更新为AsyncData,Widget也不会重建,自然看不到更新后的状态。- 当前Mock的状态重写方式不符合Riverpod规范,直接重写
stategetter会干扰状态初始化流程,导致异步逻辑和状态不同步。
具体修复步骤
1. 修正Mock Provider的状态逻辑
不要直接重写state getter,而是在build方法中直接返回已完成的Future,或者同步设置状态:
class MockNewUserProvider extends AutoDisposeAsyncNotifier<UserModel?> with Mock implements NewUser { @override Future<UserModel?> build() async { // 直接返回已完成的Future,跳过异步等待 return Future.value(users.first); } @override Future<UserModel?> getUser() async { return users.first; } }
或者更直接地在build中初始化状态:
class MockNewUserProvider extends AutoDisposeAsyncNotifier<UserModel?> with Mock implements NewUser { @override Future<UserModel?> build() { // 立即设置为数据状态,跳过loading阶段 state = AsyncData(users.first); return Future.value(users.first); } @override Future<UserModel?> getUser() async { return users.first; } }
2. 处理Widget中read的状态更新问题
如果必须用read,需要手动监听状态变化触发重建:
@override Widget build(BuildContext context, WidgetRef ref) { var userState = ref.read(newUserProvider); // 手动监听状态变化,更新时触发重建 ref.listen<AsyncValue<UserModel?>>(newUserProvider, (previous, next) { if (previous != next) { setState(() {}); } }); return userState.when( loading: () => const CircularProgressIndicator(), data: (user) => Text(user?.name ?? '无用户'), error: (err, stack) => Text('错误:$err'), ); }
注意:这种方式本质是模拟watch的行为,若非必要,直接用watch更简洁。
3. 调整测试代码确保状态初始化完成
测试时强制等待Provider初始化完成,再执行断言:
testWidgets('My widget loaded correctly', (tester) async { await tester.runAsync(() async { await tester.pumpApp( MyWidget(), overrides: [ newUserProvider.overrideWith(() => mockMockNewUserProvider) ], ); // 等待Provider的Future完成,确保状态更新 await tester.widget<MyWidget>(find.byType(MyWidget)).ref.read(newUserProvider.future); await tester.pumpAndSettle(); // 执行断言,比如检查用户名称是否显示 expect(find.text(users.first.name), findsOneWidget); }); });
内容的提问来源于stack exchange,提问作者Cyrus the Great
相关产品推荐
相关产品推荐

