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);
});
Другие вопросы по тегам