我的一些小部件具有根據狀態顯示/隱藏元素的條件 UI。我正在嘗試設定根據狀態(例如,用戶角色)查找或不查找小部件的測驗。我下面的代碼示例被簡化為一個小部件及其狀態的基礎知識,因為我似乎無法獲得狀態架構的最基本實作來處理模擬。
當我遵循以下其他示例時:
- https://bartvwezel.nl/flutter/flutter-riverpod-testing-example/
- https://stackoverflow.com/a/66122516/8177355
我無法訪問.state覆寫陣列中的值。嘗試運行測驗時,我也收到以下錯誤。這與mocktail 和mockito 相同。我只能訪問.notifier要覆寫的值(請參閱此處答案下評論中的類似問題:https : //stackoverflow.com/a/68964548/8177355)
我想知道是否有人可以幫助我或提供示例來說明如何使用這種特定的 Riverpod 狀態架構進行模擬。
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following ProviderException was thrown building LanguagePicker(dirty, dependencies:
[UncontrolledProviderScope], state: _ConsumerState#9493f):
An exception was thrown while building Provider<Locale>#1de97.
Thrown exception:
An exception was thrown while building StateNotifierProvider<LocaleStateNotifier,
LocaleState>#473ab.
Thrown exception:
type 'Null' is not a subtype of type '() => void'
Stack trace:
#0 MockStateNotifier.addListener (package:state_notifier/state_notifier.dart:270:18)
#1 StateNotifierProvider.create (package:riverpod/src/state_notifier_provider/base.dart:60:37)
#2 ProviderElementBase._buildState (package:riverpod/src/framework/provider_base.dart:481:26)
#3 ProviderElementBase.mount (package:riverpod/src/framework/provider_base.dart:382:5)
...[hundreds more lines]
示例代碼
Riverpod的東西
import 'dart:ui';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpodlocalization/models/locale/locale_providers.dart';
import 'package:riverpodlocalization/models/persistent_state.dart';
import 'package:riverpodlocalization/utils/json_local_sync.dart';
import 'locale_json_converter.dart';
part 'locale_state.freezed.dart';
part 'locale_state.g.dart';
// Fallback Locale
const Locale fallbackLocale = Locale('en', 'US');
final localeStateProvider = StateNotifierProvider<LocaleStateNotifier, LocaleState>((ref) => LocaleStateNotifier(ref));
@freezed
class LocaleState with _$LocaleState, PersistentState<LocaleState> {
const factory LocaleState({
@LocaleJsonConverter() @Default(fallbackLocale) @JsonKey() Locale locale,
}) = _LocaleState;
// Allow custom getters / setters
const LocaleState._();
static const _localStorageKey = 'persistentLocale';
/// Local Save
/// Saves the settings to persistent storage
@override
Future<bool> localSave() async {
Map<String, dynamic> value = toJson();
try {
return await JsonLocalSync.save(key: _localStorageKey, value: value);
} catch (e) {
print(e);
return false;
}
}
/// Local Delete
/// Deletes the settings from persistent storage
@override
Future<bool> localDelete() async {
try {
return await JsonLocalSync.delete(key: _localStorageKey);
} catch (e) {
print(e);
return false;
}
}
/// Create the settings from Persistent Storage
/// (Static Factory Method supports Async reading of storage)
@override
Future<LocaleState?> fromStorage() async {
try {
var _value = await JsonLocalSync.get(key: _localStorageKey);
if (_value == null) {
return null;
}
var _data = LocaleState.fromJson(_value);
return _data;
} catch (e) {
rethrow;
}
}
// For Riverpod integrated toJson / fromJson json_serializable code generator
factory LocaleState.fromJson(Map<String, dynamic> json) => _$LocaleStateFromJson(json);
}
class LocaleStateNotifier extends StateNotifier<LocaleState> {
final StateNotifierProviderRef ref;
LocaleStateNotifier(this.ref) : super(const LocaleState());
/// Initialize Locale
/// Can be run at startup to establish the initial local from storage, or the platform
/// 1. Attempts to restore locale from storage
/// 2. IF no locale in storage, attempts to set local from the platform settings
Future<void> initLocale() async {
// Attempt to restore from storage
bool _fromStorageSuccess = await ref.read(localeStateProvider.notifier).restoreFromStorage();
// If storage restore did not work, set from platform
if (!_fromStorageSuccess) {
ref.read(localeStateProvider.notifier).setLocale(ref.read(platformLocaleProvider));
}
}
/// Set Locale
/// Attempts to set the locale if it's in our list of supported locales.
/// IF NOT: get the first locale that matches our language code and set that
/// ELSE: do nothing.
void setLocale(Locale locale) {
List<Locale> _supportedLocales = ref.read(supportedLocalesProvider);
// Set the locale if it's in our list of supported locales
if (_supportedLocales.contains(locale)) {
// Update state
state = state.copyWith(locale: locale);
// Save to persistence
state.localSave();
return;
}
// Get the closest language locale and set that instead
Locale? _closestLocale =
_supportedLocales.firstWhereOrNull((supportedLocale) => supportedLocale.languageCode == locale.languageCode);
if (_closestLocale != null) {
// Update state
state = state.copyWith(locale: _closestLocale);
// Save to persistence
state.localSave();
return;
}
// Otherwise, do nothing and we'll stick with the default locale
return;
}
/// Restore Locale from Storage
Future<bool> restoreFromStorage() async {
try {
print("Restoring LocaleState from storage.");
// Attempt to get the user from storage
LocaleState? _state = await state.fromStorage();
// If user is null, there is no user to restore
if (_state == null) {
return false;
}
print("State found in storage: " _state.toJson().toString());
// Set state
state = _state;
return true;
} catch (e, s) {
print("Error" e.toString());
print(s);
return false;
}
}
}
小部件試圖測驗
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpodlocalization/models/locale/locale_providers.dart';
import 'package:riverpodlocalization/models/locale/locale_state.dart';
import 'package:riverpodlocalization/models/locale/locale_translate_name.dart';
class LanguagePicker extends ConsumerWidget {
const LanguagePicker({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
Locale _currentLocale = ref.watch(localeProvider);
List<Locale> _supportedLocales = ref.read(supportedLocalesProvider);
print("Current Locale: " _currentLocale.toLanguageTag());
return DropdownButton<Locale>(
isDense: true,
value: (!_supportedLocales.contains(_currentLocale)) ? null : _currentLocale,
icon: const Icon(Icons.arrow_drop_down),
underline: Container(
height: 1,
color: Colors.black26,
),
onChanged: (Locale? newLocale) {
if (newLocale == null) {
return;
}
print("Selected " newLocale.toString());
// Set the locale (this will rebuild the app)
ref.read(localeStateProvider.notifier).setLocale(newLocale);
return;
},
// Create drop down items from our supported locales
items: _supportedLocales
.map<DropdownMenuItem<Locale>>(
(locale) => DropdownMenuItem<Locale>(
value: locale,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
translateLocaleName(locale: locale),
),
),
),
)
.toList());
}
}
測驗檔案
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:riverpodlocalization/models/locale/locale_state.dart';
import 'package:riverpodlocalization/widgets/language_picker.dart';
class MockStateNotifier extends Mock implements LocaleStateNotifier {}
void main() {
final mockStateNotifier = MockStateNotifier();
Widget testingWidget() {
return ProviderScope(
overrides: [localeStateProvider.overrideWithValue(mockStateNotifier)],
child: const MaterialApp(
home: LanguagePicker(),
),
);
}
testWidgets('Test that the pumpedWidget is loaded with our above mocked state', (WidgetTester tester) async {
await tester.pumpWidget(testingWidget());
});
}
uj5u.com熱心網友回復:
示例存盤庫
我能夠使用 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(),
),
);
}
我們主螢屏的這個測驗小部件使用 的overrides屬性ProviderScope()來覆寫小部件中使用的提供程式。
當 home.dartScreenHome()小部件呼叫時Counter counter = ref.watch(counterProvider);,它將使用我們的mockProvider而不是“真正的”提供者。
該isEvenTestWidget()mockProvider引數是提供者的相同的“型別” 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);
});
在測驗中,我們使用測驗ScreenHome()小部件呈現所需的預定義值創建了一個 mockProvider 。在這個例子中,我們的 provider 是用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);
}
}
為了能夠創建具有預定義狀態的 mockProvider,StateNotifier( counter_state.dart) 建構式包含狀態模型的可選引數非常重要。該默認引數是怎樣的狀態應該正常初始化。我們的測驗可以選擇提供一個指定的測驗狀態,該狀態被傳遞到super().
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/360893.html
下一篇:在matlab中表現不佳
