【问题标题】:Dart Testing with Riverpod StateNotifierProvider and AsyncValue as state使用 Riverpod StateNotifierProvider 和 AsyncValue 作为状态的 Dart 测试
【发布时间】:2022-06-11 00:27:33
【问题描述】:

这是我第一个使用 Dart/Flutter/Riverpod 的应用程序,欢迎对代码提出任何建议或意见。

我使用Hive 作为嵌入式数据库,因此提供者状态的初始值是异步加载的,并使用riverpodAsyncValue 来包装它。

以下代码有效,但我对测试方法有一些疑问,所以我想确认一下我是否按照应该使用的那样使用 Riverpod 库。

这是我的提供者及其部门(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));
});

以下是测试用例,我在嘲笑 Hive 盒子提供者 (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);
    // Next line waits until we have a value for preferences attribute
    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 的 official docs 和与 AsyncValues 相关的 GitHub issue 作为参考

好吧,我发现了一些问题来验证是否使用正确的值调用了侦听器,我使用TypeMatcher 只是为了验证状态实例是否具有正确的类型,然后我检查(“手动”)的值如果它是预期的,则被包装对象的属性。有没有更好的方法来实现这一点?

最后,我没有找到太多以StateNotifierAsyncValue 作为状态类型的示例,是否有更好的方法来实现使用延迟数据初始化的提供程序?

【问题讨论】:

    标签: unit-testing dart asynchronous riverpod dart-test


    【解决方案1】:

    我不太喜欢我原来的方法,所以我创建了自己的 Matcher 来比较 AsyncValue 实例中的包装值:

    class IsWrappedValueEquals extends Matcher {
      final dynamic value;
    
      IsWrappedValueEquals(this.value);
    
      @override
      bool matches(covariant AsyncValue actual, Map<dynamic, dynamic> matchState) => 
        equals(actual.value).matches(value, matchState);
    
      @override
      Description describe(Description description) => description.add('Is wrapped value equals');
    }
    
    

    在测试中,最后的部分有点不同:

        Preferences preferences = Preferences.from(container.read(preferencesProvider.notifier).preferences!);
        
        preferences.currentListName = 'Lista1';
        await container.read(preferencesProvider.notifier).save(preferences);
    
    // the following line is the new one
        verify(listener(IsWrappedValueEquals(Preferences()), IsWrappedValueEquals(preferences))).called(1);
        verifyNoMoreInteractions(listener);
    }
    

    比起原始代码,我更喜欢我自定义的Matcher,但我觉得自定义代码太多,无法测试某些东西,显然,很常见。

    如果有人能告诉我这个案例的更好解决方案,那就太好了。

    【讨论】:

      猜你喜欢
      • 2021-12-28
      • 2021-03-24
      • 1970-01-01
      • 2022-08-14
      • 2023-01-25
      • 1970-01-01
      • 1970-01-01
      • 2021-10-28
      • 2021-04-01
      相关资源
      最近更新 更多