You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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规范,直接重写state getter会干扰状态初始化流程,导致异步逻辑和状态不同步。

具体修复步骤

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 23:30:12