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

Dart中Riverpod的StateNotifierProvider与AsyncValue测试及实现咨询

Dart/Flutter 基于Riverpod+Hive的开发问题

这是我首次开发基于Dart/Flutter/Riverpod的应用,欢迎对代码提出任何建议或意见。

我采用Hive作为嵌入式数据库,因此provider状态的初始值为异步加载,使用Riverpod提供的AsyncValue对该异步状态进行包装。

下述代码可正常运行,但我对测试方案存在疑问,希望确认当前对Riverpod库的使用方式是否符合规范。

Provider及依赖实现代码

Preferences是用于存储应用通用配置数据的HiveObject,相关实现代码如下:

final hiveProvider = FutureProvider<HiveInterface>((ref) async {
  return await App.setUp();
});

final prefBoxProvider = FutureProvider<Box<Preferences>>((ref) async {
  final HiveInterface hive = await ref.read(hiveProvider.future);
  return hive.openBox<Preferences>("preferences");
});

class PreferencesNotifier extends StateNotifier<AsyncValue<Preferences>> {
  late Box<Preferences> prefBox;

  PreferencesNotifier(Future<Box<Preferences>> prefBoxFuture): super(const AsyncValue.loading()) {
    prefBoxFuture.then((value) {
      prefBox = value;
      _loadCurrentPreferences();
    });
  }

  void _loadCurrentPreferences() {
    Preferences pref = prefBox.get(0) ?? Preferences();
    state = AsyncValue.data(pref);    
  }

  Future<void> save(Preferences prefs) async {    
    await prefBox.put(0, prefs);
    state = AsyncValue.data(prefs);
  }

  Preferences? get preferences {    
    return state.when(data: (value) => value,
    error: (_, __) => null,
    loading: () => null);
  }

}


final preferencesProvider = StateNotifierProvider<PreferencesNotifier, AsyncValue<Preferences>>((ref) {
  return PreferencesNotifier(ref.read(prefBoxProvider.future));
});

测试用例实现

测试中我mock了Hive box provider(prefBoxProvider),代码如下:

class Listener extends Mock {
  void call(dynamic previous, dynamic value);
}

Future<Box<Preferences>> prefBoxTesting() async {
  final hive = await App.setUp();
  Box<Preferences> box = await hive.openBox<Preferences>("testing_preferences");
  await box.clear();
  return box;
}

void main() {
  
  test('Preferences value changes', () async {

    final container = ProviderContainer(overrides: [
        prefBoxProvider.overrideWithValue(AsyncValue.data(await prefBoxTesting()))
    ],);
    addTearDown(() {
      container.dispose();
      Hive.deleteBoxFromDisk("testing_preferences");
    });
    final listener = Listener();

    container.listen<AsyncValue<Preferences>>(
      preferencesProvider,
      listener,
      fireImmediately: true,
    );
    verify(listener(null, const TypeMatcher<AsyncLoading>())).called(1);
    verifyNoMoreInteractions(listener);
    // 等待preferences属性加载完成
    await container.read(preferencesProvider.notifier).stream.first;
    verify(listener(const TypeMatcher<AsyncLoading>(), const TypeMatcher<AsyncData>())).called(1);
    
    Preferences preferences = Preferences.from(container.read(preferencesProvider.notifier).preferences!);
    
    preferences.currentListName = 'Lista1';
    await container.read(preferencesProvider.notifier).save(preferences);
    
    verify(listener(const TypeMatcher<AsyncData>(), const TypeMatcher<AsyncData>())).called(1);
    verifyNoMoreInteractions(listener);
    final name = container.read(preferencesProvider.notifier).preferences!.currentListName;
    expect(name, equals('Lista1'));
   });

}

我参考Riverpod官方测试文档以及AsyncValue相关GitHub issue编写了上述代码。

待解决的两个问题

  • 验证listener被传入正确参数值时存在障碍:当前仅使用TypeMatcher验证状态实例的类型是否正确,再手动检查包装对象的属性值是否符合预期,想了解是否存在更优的实现方式。
  • 目前可参考的将StateNotifier与AsyncValue结合作为状态类型的示例较少,想了解针对依赖延迟加载数据初始化的provider,是否存在更优的实现方案。

内容的提问来源于stack exchange,提问作者Roberto

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 04:33:24