Flutter имитирует ChangeNotifierProvider и устанавливает значения провайдера
Хорошо, терпите меня, поскольку я только начинаю изучать тестирование виджетов. У меня есть виджет MainAppBar, над которым в дереве виджетов находится AuthProvider, который необходим для проверки аутентификации и последующего выхода из системы, если пользователь действительно аутентифицирован. Это глупый сценарий, но возможность его протестировать будет полезна для многих других сценариев в моем приложении.
IconButton(
icon: Icon(Icons.logout),
onPressed: () {
if (Provider.of<AuthProvider>(context, listen: false).isAuthenticated) {
Provider.of<AuthProvider>(context, listen: false).signOut();
Navigator.pushReplacementNamed(context, '/login');
}
}
)
Я издеваюсь над AuthProvider вот так
class MockAuthProvider extends Mock implements AuthProvider {}
с mockito и обертыванием виджета вот так
MockAuthProvider mockAuthProvider = MockAuthProvider();
Widget makeTestableWidget({Widget child}) => MaterialApp(
home: ChangeNotifierProvider<AuthProvider>(
create: (_) => mockAuthProvider,
child: Scaffold(
body: child,
),
),
);
В
isAuthenticated
bool основан на простой проверке статуса перечисления, поэтому, пока я устанавливаю статус, я могу тестировать истинные и ложные сценарии и т. д. В моем тесте я хотел бы установить статус и проверить,
signOut
и
Navigator
называются или нет.
testWidgets('Clicking on logout icon...', (WidgetTester tester) async {
mockAuthProvider.setAuthProviderStatus(AuthProviderStatus.Unauthenticated);
print(mockAuthProvider.isAuthenticated);
await tester.pumpWidget(makeTestableWidget(child: MainAppBar(title: 'test')));
var icon = find.byIcon(Icons.logout);
await tester.tap(icon);
await tester.pump();
verifyNever(mockAuthProvider.signOut());
});
В
setAuthProviderStatus
сеттер устанавливает статус и
isAuthenticated
выполняет базовую проверку:
bool get isAuthenticated {
if (status == AuthProviderStatus.Authenticated) {
return true;
}
return false;
}
AuthProvider инициализирует статус по умолчанию
Uninitialized
, поэтому, хотя эта проверка может быть расширена до нулевого значения, на данный момент все в порядке. В моем виджете MainAppBar значок
isAuthenticated
bool возвращается как null и выдает исключение
Failed assertion: boolean expression must not be null
поскольку для оператора if требуется логическое значение, а значение null вызывает сбой. Печать в моем тесте
print(mockAuthProvider.isAuthenticated);
также равно нулю. Я успешно протестировал передачу фактического AuthProvider, и у меня нет такой же проблемы, хотя это явно не сработает для целей тестирования.
Как я могу установить значения в фиктивном провайдере, чтобы виджеты, находящиеся под ними, знали о них?
1 ответ
Если кто-то наткнется на это... Для этого вы можете при необходимости отключить любую функциональность провайдера:
Вместо:
mockAuthProvider.setAuthProviderStatus(AuthProviderStatus.Unauthenticated);
вы можете использовать mockito так:
when(mockAuthProvider.isAuthenticated)
.thenReturn(isAuthenticated);
так что когда линия
Provider.of<AuthProvider>(context, listen: false).isAuthenticated
выполняется, isAuthenticated будет иметь заглушенное значение.
Вот тесты, которые я написал для этой функции Icon onPressed:
void setIsAuthenticated(bool isAuthenticated) {
when(mockAuthProvider.isAuthenticated)
.thenReturn(isAuthenticated);
when(mockAuthProvider.hasListeners).thenReturn(false);
}
testWidgets('Clicking on logout icon does not call signOut if user is not authenticated', (WidgetTester tester) async {
setIsAuthenticated(false);
await tester.pumpWidget(makeTestableWidget(child: MainAppBar(title: 'Test')));
var icon = find.byIcon(Icons.logout);
await tester.tap(icon);
await tester.pump();
verifyNever(mockAuthProvider.signOut());
});
testWidgets('Clicking on logout icon calls signOut if user is '
'authenticated AND push replace route',
(WidgetTester tester) async {
setIsAuthenticated(true);
await tester.pumpWidget(makeTestableWidget(child: MainAppBar(title: 'Test')));
var icon = find.byIcon(Icons.logout);
await tester.tap(icon);
await tester.pumpAndSettle();
verify(mockAuthProvider.signOut());
verify(mockObserver.didPush(any, any));
expect(find.text('Login'), findsOneWidget);
});