Mat*_*out 8 testing mockito dart flutter
我的一些小部件具有条件 UI,可以根据状态显示/隐藏元素。我正在尝试设置根据状态(例如,用户角色)查找或不查找小部件的测试。我下面的代码示例被精简为一个小部件及其状态的基础知识,因为我似乎无法获得状态架构的最基本的实现来与模拟一起使用。
\n当我遵循其他示例时,如下所示:
\n\n我无法访问.state覆盖数组中的值。尝试运行测试时我还收到以下错误。这与无酒精鸡尾酒和无酒精鸡尾酒相同。我只能访问.notifier要覆盖的值(请参阅此处答案下的评论中的类似问题:https ://stackoverflow.com/a/68964548/8177355 )
我想知道是否有人可以帮助我或提供如何模拟这种特定的 Riverpod 状态架构的示例。
\n\xe2\x95\x90\xe2\x95\x90\xe2\x95\xa1 EXCEPTION CAUGHT BY WIDGETS LIBRARY \xe2\x95\x9e\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\nThe following ProviderException was thrown building LanguagePicker(dirty, dependencies:\n[UncontrolledProviderScope], state: _ConsumerState#9493f):\nAn exception was thrown while building Provider<Locale>#1de97.\n\nThrown exception:\nAn exception was thrown while building StateNotifierProvider<LocaleStateNotifier,\nLocaleState>#473ab.\n\nThrown exception:\ntype \'Null\' is not a subtype of type \'() => void\'\n\nStack trace:\n#0 MockStateNotifier.addListener (package:state_notifier/state_notifier.dart:270:18)\n#1 StateNotifierProvider.create (package:riverpod/src/state_notifier_provider/base.dart:60:37)\n#2 ProviderElementBase._buildState (package:riverpod/src/framework/provider_base.dart:481:26)\n#3 ProviderElementBase.mount (package:riverpod/src/framework/provider_base.dart:382:5)\n...[hundreds more lines]\nRun Code Online (Sandbox Code Playgroud)\n河波德的东西
\n\xe2\x95\x90\xe2\x95\x90\xe2\x95\xa1 EXCEPTION CAUGHT BY WIDGETS LIBRARY \xe2\x95\x9e\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\nThe following ProviderException was thrown building LanguagePicker(dirty, dependencies:\n[UncontrolledProviderScope], state: _ConsumerState#9493f):\nAn exception was thrown while building Provider<Locale>#1de97.\n\nThrown exception:\nAn exception was thrown while building StateNotifierProvider<LocaleStateNotifier,\nLocaleState>#473ab.\n\nThrown exception:\ntype \'Null\' is not a subtype of type \'() => void\'\n\nStack trace:\n#0 MockStateNotifier.addListener (package:state_notifier/state_notifier.dart:270:18)\n#1 StateNotifierProvider.create (package:riverpod/src/state_notifier_provider/base.dart:60:37)\n#2 ProviderElementBase._buildState (package:riverpod/src/framework/provider_base.dart:481:26)\n#3 ProviderElementBase.mount (package:riverpod/src/framework/provider_base.dart:382:5)\n...[hundreds more lines]\nRun Code Online (Sandbox Code Playgroud)\n小部件尝试测试
\nimport \'dart:ui\';\n\nimport \'package:flutter_riverpod/flutter_riverpod.dart\';\nimport \'package:freezed_annotation/freezed_annotation.dart\';\nimport \'package:riverpodlocalization/models/locale/locale_providers.dart\';\nimport \'package:riverpodlocalization/models/persistent_state.dart\';\nimport \'package:riverpodlocalization/utils/json_local_sync.dart\';\n\nimport \'locale_json_converter.dart\';\n\npart \'locale_state.freezed.dart\';\npart \'locale_state.g.dart\';\n\n// Fallback Locale\nconst Locale fallbackLocale = Locale(\'en\', \'US\');\n\nfinal localeStateProvider = StateNotifierProvider<LocaleStateNotifier, LocaleState>((ref) => LocaleStateNotifier(ref));\n\n@freezed\nclass LocaleState with _$LocaleState, PersistentState<LocaleState> {\n const factory LocaleState({\n @LocaleJsonConverter() @Default(fallbackLocale) @JsonKey() Locale locale,\n }) = _LocaleState;\n\n // Allow custom getters / setters\n const LocaleState._();\n\n static const _localStorageKey = \'persistentLocale\';\n\n /// Local Save\n /// Saves the settings to persistent storage\n @override\n Future<bool> localSave() async {\n Map<String, dynamic> value = toJson();\n try {\n return await JsonLocalSync.save(key: _localStorageKey, value: value);\n } catch (e) {\n print(e);\n return false;\n }\n }\n\n /// Local Delete\n /// Deletes the settings from persistent storage\n @override\n Future<bool> localDelete() async {\n try {\n return await JsonLocalSync.delete(key: _localStorageKey);\n } catch (e) {\n print(e);\n return false;\n }\n }\n\n /// Create the settings from Persistent Storage\n /// (Static Factory Method supports Async reading of storage)\n @override\n Future<LocaleState?> fromStorage() async {\n try {\n var _value = await JsonLocalSync.get(key: _localStorageKey);\n if (_value == null) {\n return null;\n }\n var _data = LocaleState.fromJson(_value);\n return _data;\n } catch (e) {\n rethrow;\n }\n }\n\n // For Riverpod integrated toJson / fromJson json_serializable code generator\n factory LocaleState.fromJson(Map<String, dynamic> json) => _$LocaleStateFromJson(json);\n}\n\nclass LocaleStateNotifier extends StateNotifier<LocaleState> {\n final StateNotifierProviderRef ref;\n LocaleStateNotifier(this.ref) : super(const LocaleState());\n\n /// Initialize Locale\n /// Can be run at startup to establish the initial local from storage, or the platform\n /// 1. Attempts to restore locale from storage\n /// 2. IF no locale in storage, attempts to set local from the platform settings\n Future<void> initLocale() async {\n // Attempt to restore from storage\n bool _fromStorageSuccess = await ref.read(localeStateProvider.notifier).restoreFromStorage();\n\n // If storage restore did not work, set from platform\n if (!_fromStorageSuccess) {\n ref.read(localeStateProvider.notifier).setLocale(ref.read(platformLocaleProvider));\n }\n }\n\n /// Set Locale\n /// Attempts to set the locale if it\'s in our list of supported locales.\n /// IF NOT: get the first locale that matches our language code and set that\n /// ELSE: do nothing.\n void setLocale(Locale locale) {\n List<Locale> _supportedLocales = ref.read(supportedLocalesProvider);\n\n // Set the locale if it\'s in our list of supported locales\n if (_supportedLocales.contains(locale)) {\n // Update state\n state = state.copyWith(locale: locale);\n\n // Save to persistence\n state.localSave();\n return;\n }\n\n // Get the closest language locale and set that instead\n Locale? _closestLocale =\n _supportedLocales.firstWhereOrNull((supportedLocale) => supportedLocale.languageCode == locale.languageCode);\n if (_closestLocale != null) {\n // Update state\n state = state.copyWith(locale: _closestLocale);\n\n // Save to persistence\n state.localSave();\n return;\n }\n\n // Otherwise, do nothing and we\'ll stick with the default locale\n return;\n }\n\n /// Restore Locale from Storage\n Future<bool> restoreFromStorage() async {\n try {\n print("Restoring LocaleState from storage.");\n // Attempt to get the user from storage\n LocaleState? _state = await state.fromStorage();\n\n // If user is null, there is no user to restore\n if (_state == null) {\n return false;\n }\n\n print("State found in storage: " + _state.toJson().toString());\n\n // Set state\n state = _state;\n\n return true;\n } catch (e, s) {\n print("Error" + e.toString());\n print(s);\n return false;\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n测试文件
\nimport \'package:flutter/material.dart\';\nimport \'package:flutter_riverpod/flutter_riverpod.dart\';\nimport \'package:riverpodlocalization/models/locale/locale_providers.dart\';\nimport \'package:riverpodlocalization/models/locale/locale_state.dart\';\nimport \'package:riverpodlocalization/models/locale/locale_translate_name.dart\';\n\nclass LanguagePicker extends ConsumerWidget {\n const LanguagePicker({Key? key}) : super(key: key);\n\n @override\n Widget build(BuildContext context, WidgetRef ref) {\n Locale _currentLocale = ref.watch(localeProvider);\n List<Locale> _supportedLocales = ref.read(supportedLocalesProvider);\n\n print("Current Locale: " + _currentLocale.toLanguageTag());\n\n return DropdownButton<Locale>(\n isDense: true,\n value: (!_supportedLocales.contains(_currentLocale)) ? null : _currentLocale,\n icon: const Icon(Icons.arrow_drop_down),\n underline: Container(\n height: 1,\n color: Colors.black26,\n ),\n onChanged: (Locale? newLocale) {\n if (newLocale == null) {\n return;\n }\n print("Selected " + newLocale.toString());\n\n // Set the locale (this will rebuild the app)\n ref.read(localeStateProvider.notifier).setLocale(newLocale);\n\n return;\n },\n // Create drop down items from our supported locales\n items: _supportedLocales\n .map<DropdownMenuItem<Locale>>(\n (locale) => DropdownMenuItem<Locale>(\n value: locale,\n child: Padding(\n padding: const EdgeInsets.symmetric(horizontal: 8.0),\n child: Text(\n translateLocaleName(locale: locale),\n ),\n ),\n ),\n )\n .toList());\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n
我能够使用 StateNotifierProvider 成功模拟状态/提供者。我在这里创建了一个独立的存储库并进行了细分: https: //github.com/mdrideout/testing-state-notifier-provider
这无需 Mockito / Mocktail 即可工作。
为了在使用 StateNotifier 和 StateNotifierProvider 时模拟您的状态,您的 StateNotifier 类必须包含状态模型的可选参数,以及状态应如何初始化的默认值。在您的测试中,您可以将具有预定义状态的模拟提供程序传递给您的测试小部件,并使用overrides来覆盖您的模拟提供程序。
请参阅上面链接的存储库以获取完整代码
测试小部件
Widget isEvenTestWidget(StateNotifierProvider<CounterNotifier, Counter> mockProvider) {
return ProviderScope(
overrides: [
counterProvider.overrideWithProvider(mockProvider),
],
child: const MaterialApp(
home: ScreenHome(),
),
);
}
Run Code Online (Sandbox Code Playgroud)
我们的主屏幕的这个测试小部件使用 的overrides属性ProviderScope()来覆盖小部件中使用的提供程序。
当 home.dartScreenHome()小部件调用时Counter counter = ref.watch(counterProvider);,它将使用我们的mockProvider而不是“真正的”提供者。
mockProvider参数isEvenTestWidget()与提供者的“类型”相同counterProvider()。
考试
testWidgets('If count is even, IsEvenMessage is rendered.', (tester) async {
// Mock a provider with an even count
final mockCounterProvider =
StateNotifierProvider<CounterNotifier, Counter>((ref) => CounterNotifier(counter: const Counter(count: 2)));
await tester.pumpWidget(isEvenTestWidget(mockCounterProvider));
expect(find.byType(IsEvenMessage), findsOneWidget);
});
Run Code Online (Sandbox Code Playgroud)
在测试中,我们使用测试小部件渲染所需的预定义值创建一个模拟提供程序ScreenHome()。在此示例中,我们的提供程序使用state 进行初始化count: 2。
我们正在测试isEvenMessage()小部件是否以偶数(2)呈现。另一个测试测试小部件未以奇数计数呈现。
状态通知器构造函数
class CounterNotifier extends StateNotifier<Counter> {
CounterNotifier({Counter counter = const Counter(count: 0)}) : super(counter);
void increment() {
state = state.copyWith(count: state.count + 1);
}
}
Run Code Online (Sandbox Code Playgroud)
为了能够创建具有预定义状态的mockProvider,StateNotifier( counter_state.dart)构造函数包含状态模型的可选参数是很重要的。默认参数是状态通常应该如何初始化。我们的测试可以选择提供指定的测试状态,并将其传递给super().
| 归档时间: |
|
| 查看次数: |
4793 次 |
| 最近记录: |