Flutter - Mockito ведет себя странно, когда пытается создать пользовательское исключение - PullRequest
0 голосов
/ 11 сентября 2018

Пытаясь использовать Mockito для тестирования моего BLoC, BLoC выполняет серверный вызов с использованием класса репозитория, и предполагается, что функция серверного вызова выдает пользовательское исключение, если пользователь не аутентифицирован.

Но когда я пытаюсь заглушить функцию хранилища, чтобы вызвать это пользовательское исключение, тест просто завершается ошибкой:

sunapsis Authorization error (test error): test description

package:mockito/src/mock.dart 342:7                                     PostExpectation.thenThrow.<fn>
package:mockito/src/mock.dart 119:37                                    Mock.noSuchMethod
package:sunapsis/datasource/models/notifications_repository.dart 28:37  MockNotificationRepository.getNotificationList
package:sunapsis/blocs/notification_blocs/notification_bloc.dart 36:10  NotificationBloc.fetchNotifications
test/blocs/notification_blocs/notification_bloc_test.dart 53:48         main.<fn>.<fn>.<fn>
===== asynchronous gap ===========================
dart:async                                                              scheduleMicrotask
test/blocs/notification_blocs/notification_bloc_test.dart 53:7          main.<fn>.<fn>

И вот как выглядит мой код BLoC: fetchNotifications функция вызывает функцию хранилища и обрабатывает ответ и ошибки. Существует два блока catchError, один обрабатывает регистр AuthorizationException, а другой обрабатывает любое другое исключение. Обработка AuthorizationException по-разному, поскольку она будет использоваться для установки состояния входа в приложение.

notification_bloc.dart

import 'dart:async';

import 'package:logging/logging.dart';
import 'package:rxdart/rxdart.dart';
import 'package:sunapsis/datasource/dataobjects/notification.dart';
import 'package:sunapsis/datasource/models/notifications_repository.dart';
import 'package:sunapsis/utils/authorization_exception.dart';

class NotificationBloc {
  final NotificationsRepository _notificationsRepository;

  final Logger log = Logger('NotificationBloc');
  final _listNotifications = PublishSubject<List<NotificationElement>>();
  final _isEmptyList = PublishSubject<bool>();
  final _isLoggedIn = PublishSubject<bool>();

  Observable<List<NotificationElement>> get getNotificationList =>
      _listNotifications.stream;

  Observable<bool> get isLoggedIn => _isLoggedIn.stream;

  Observable<bool> get isEmptyList => _isEmptyList.stream;

  NotificationBloc({NotificationsRepository notificationsRepository})
      : _notificationsRepository =
            notificationsRepository ?? NotificationsRepository();

  void fetchNotifications() {
    _notificationsRepository
        .getNotificationList()
        .then((List<NotificationElement> list) {
          if (list.length > 0) {
            _listNotifications.add(list);
          } else {
            _isEmptyList.add(true);
          }
        })
        .catchError((e) => _handleErrorCase,
            test: (e) => e is AuthorizationException)
        .catchError((e) {
          log.shout("Error occurred while fetching notifications $e");
          _listNotifications.sink.addError("$e");
        });
  }
  void _handleErrorCase(e) {
     log.shout("Session invalid: $e");
     _isLoggedIn.sink.add(false);
     _listNotifications.sink.addError("Error");
 }
}

Вот как выглядит мой код репозитория:

notifications_repository.dart

import 'dart:async';

import 'package:logging/logging.dart';
import 'package:sunapsis/datasource/dataobjects/notification.dart';
import 'package:sunapsis/datasource/db/sunapsis_db_provider.dart';
import 'package:sunapsis/datasource/network/api_response.dart';
import 'package:sunapsis/datasource/network/sunapsis_api_provider.dart';
import 'package:sunapsis/utils/authorization_exception.dart';

/// Repository class which makes available all notifications related API functions
/// for server calls and database calls
class NotificationsRepository {
  final Logger log = Logger('NotificationsRepository');
  final SunapsisApiProvider apiProvider;
  final SunapsisDbProvider dbProvider;

  /// Optional [SunapsisApiProvider] and [SunapsisDbProvider] instances expected for unit testing
  /// If instances are not provided - default case - a new instance is created
  NotificationsRepository({SunapsisApiProvider api, SunapsisDbProvider db})
      : apiProvider = api ?? SunapsisApiProvider(),
        dbProvider = db ?? SunapsisDbProvider();

  /// Returns a [Future] of [List] of [NotificationElement]
  /// Tries to first look for notifications on the db
  /// if notifications are found that list is returned
  /// else a server call is made to fetch notifications
  Future<List<NotificationElement>> getNotificationList([int currentTime]) {
    return dbProvider.fetchNotifications().then(
        (List<NotificationElement> notifications) {
      if (notifications.length == 0) {
        return getNotificationsListFromServer(currentTime);
      }
      return notifications;
    }, onError: (_) {
      return getNotificationsListFromServer(currentTime);
    });
  }
}

Функция getNotificationsListFromServer должна выдавать AuthorizationException, которая должна распространяться через getNotificationList

Это тестовый пример, который завершается с ошибкой, упомянутой ранее:

test('getNotification observable gets error on AuthorizationException',
    () async {
  when(mockNotificationsRepository.getNotificationList())
      .thenThrow(AuthorizationException("test error", "test description"));
  scheduleMicrotask(() => notificationBloc.fetchNotifications());
  await expectLater(
      notificationBloc.getNotificationList, emitsError("Error"));
});

А вот так выглядит пользовательское исключение:

authorization_exception.dart

class AuthorizationException implements Exception {
  final String error;

  final String description;

  AuthorizationException(this.error, this.description);

  String toString() {
    var header = 'sunapsis Authorization error ($error)';
    if (description != null) {
      header = '$header: $description';
    }
    return '$header';
  }
}

PS: Когда я тестировал свой класс репозитория и функцию, генерирующую пользовательское исключение, эти тесты были пройдены.

test('throws AuthorizationException on invalidSession()', () async {
  when(mockSunapsisDbProvider.fetchNotifications())
      .thenAnswer((_) => Future.error("Error"));
  when(mockSunapsisDbProvider.getCachedLoginSession(1536333713))
      .thenAnswer((_) => Future.value(authorization));
  when(mockSunapsisApiProvider.getNotifications(authHeader))
      .thenAnswer((_) => Future.value(ApiResponse.invalidSession()));
  expect(notificationsRepository.getNotificationList(1536333713),
      throwsA(TypeMatcher<AuthorizationException>()));
});

Вышеуказанный тест пройден и работает, как и ожидалось.

Я - новый выпускник колледжа, работаю на своей первой роли на полную ставку, и, возможно, я делаю что-то не так. Буду очень признателен за любые отзывы или помощь, все помогает. Спасибо за рассмотрение этого вопроса.

1 Ответ

0 голосов
/ 12 сентября 2018

Я думаю, что вы используете неправильный TypeMatcher класс. Вам нужно использовать тот из инфраструктуры тестирования, а не тот из платформы Flutter.

import 'package:flutter_test/flutter_test.dart';
import 'package:matcher/matcher.dart';

class AuthorizationException implements Exception {
  const AuthorizationException();
}

Future<List<String>> getNotificationList(int id) async {
  throw AuthorizationException();
}

void main() {
  test('getNotification observable gets error on AuthorizationException',
  () async {
    expect(getNotificationList(1536333713),
      throwsA(const TypeMatcher<AuthorizationException>()));
  });
}
...