web-dev-qa-db-fra.com

Comment attendre qu'un futur se termine dans le test du widget Flutter?

J'essaie d'effectuer un test de widget, en particulier un test de navigation. J'utilise l'architecture de bloc, la définition d'un flux sur le bloc déclenche une série d'événements à l'intérieur du bloc, obtient les informations de session de l'appel du serveur (qui retourne un futur objet de session d'informations), lors d'un appel de serveur réussi, un flux de connexion est défini et le widget a un abonnement à ce flux et navigue vers l'écran suivant.

J'utilise mockito pour se moquer de l'appel du serveur et stubbing l'appel du serveur pour renvoyer un futur de réponse de succès. Le problème est que lorsque j'appelle pumpAndSettle(), il arrive à expiration car il n'attend pas que le futur se termine et renvoie la réponse de réussite.

Je m'excuse si je ne suis pas très clair, mais voici l'exemple de code:

login_bloc.Dart

class LoginBloc {
  LoginRepository _loginRepository;
  final String searchKeyword = "special-keyword";

  final _urlString = PublishSubject<String>();
  final _isLoggedIn = BehaviorSubject<bool>(seedValue: false);
  final _errorMessage = PublishSubject<String>();

  Observable<bool> get isLoggedIn => _isLoggedIn.stream;
  Observable<String> get isErrorState => _errorMessage.stream;

  LoginBloc({LoginRepository loginRepository})
      : _loginRepository = loginRepository ?? LoginRepository() {
          // Listen on the _urlString stream to call the function which checks for the special keyword and if a match is found make a server call
    _urlString.stream.listen((String url) {
      _authorizationFullService(url);
    });
  }

    // Search for special keyword and if a match is found call the server call function
  void _authorizationFullService(String url) {
    if (url.contains(searchKeyword)) {
      int index = url.indexOf(searchKeyword);
      String result = url.substring(index + searchKeyword.length);
      result = result.trim();
      String decodedUrl = Uri.decodeFull(result);
      if (decodedUrl != null && decodedUrl.length > 0) {
        _fullServiceServerCall(decodedUrl);
      } else {
        _isLoggedIn.sink.add(false);
      }
    }
  }

    // Call server call function from repository which returns a future of the Authorization object
  void _fullServiceServerCall(String decodedUrl) {
    _loginRepository
        .getSession(decodedUrl)
        .then(_handleSuccessAuthorization)
        .catchError(_handleErrorState);
  }

    // Handle success response and set the login stream
  void _handleSuccessAuthorization(Authorization authorization) {
    if (authorization != null && authorization.idnumber != 0) {
      _isLoggedIn.sink.add(true);
    } else {
      _isLoggedIn.sink.add(false);
    }
  }

    // Handle error response and set the error stream
  void _handleErrorState(dynamic error) {
    _isLoggedIn.sink.add(false);
    _errorMessage.sink.add(error.toString());
  }

  void dispose() {
    _urlString.close();
    _isLoggedIn.close();
    _errorMessage.close();
  }
}

widget_test.Dart

group('Full Login Navigation test', () {
    LoginRepository mockLoginRepository;
    LoginBloc loginBloc;
    NotificationBloc notificationBloc;
    NavigatorObserver mockNavigatorObserver;
    Authorization _auth;
    String testUrl;

    setUp(() {
      mockLoginRepository = MockLoginRepository();
      _auth = Authorization((auth) => auth
        ..param1 = "foo"
        ..param2 = "bar"
        ..param3 = "foobar"
        ..param4 = "barfoo");
      loginBloc = LoginBloc(loginRepository: mockLoginRepository);
      mockNavigatorObserver = MockNavigatorObserver();
      testUrl = "http://test.test.com";
    });

    Future<Null> _buildFullLoginPage(LoginBloc loginBloc,
        NotificationBloc notificationBloc, WidgetTester tester) async {
      when(mockLoginRepository.getSession(testUrl))
          .thenAnswer((_) => Future.value(_auth));
      await tester.pumpWidget(LoginBlocProvider(
        child: NotificationBlocProvider(
          child: MaterialApp(
            home: LoginFullService(),
            onGenerateRoute: NavigationRoutes.routes,
            navigatorObservers: [mockNavigatorObserver],
          ),
          notificationBloc: notificationBloc,
        ),
        loginBloc: loginBloc,
      ));
      //TODO: Remove casting to dynamic after Dart sdk bug fix: https://github.com/Dart-lang/mockito/issues/163
      verify(mockNavigatorObserver.didPush(any, any) as dynamic);
      loginBloc.getAuthorization(
          "http://testing.testing.com?search-keyword=http%3A%2F%2Ftest.test.com");
    }

    testWidgets('Navigate to landing page on correct login url',
        (WidgetTester tester) async {
      await _buildFullLoginPage(loginBloc, notificationBloc, tester);
      await tester.pumpAndSettle();
      expect(find.byKey(Key('webview_scaffold')), findsNothing);
      //TODO: Remove casting to dynamic after Dart sdk bug fix: https://github.com/Dart-lang/mockito/issues/163
      verify(mockNavigatorObserver.didPush(any, any) as dynamic);
    });
});

En exécutant le widget, testez la tester.pumpAndSettle() à l'intérieur testWidgets expire avant la fin du futur. Voici le journal des erreurs:

══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following assertion was thrown running a test:
pumpAndSettle timed out

When the exception was thrown, this was the stack:
#0      WidgetTester.pumpAndSettle.<anonymous closure> (package:flutter_test/src/widget_tester.Dart:299:11)
<asynchronous suspension>
#3      TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.Dart:69:41)
#4      WidgetTester.pumpAndSettle (package:flutter_test/src/widget_tester.Dart:295:27)
#5      main.<anonymous closure>.<anonymous closure> (file:///Users/ssiddh/Documents/projects/mobile-flutter/test/ui/pages/login/login_full_test.Dart:114:20)
<asynchronous suspension>
#6      testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.Dart:72:23)
#7      TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.Dart:555:19)
<asynchronous suspension>
#10     TestWidgetsFlutterBinding._runTest (package:flutter_test/src/binding.Dart:539:14)
#11     AutomatedTestWidgetsFlutterBinding.runTest.<anonymous closure> (package:flutter_test/src/binding.Dart:883:24)
#17     AutomatedTestWidgetsFlutterBinding.runTest (package:flutter_test/src/binding.Dart:880:15)
#18     testWidgets.<anonymous closure> (package:flutter_test/src/widget_tester.Dart:71:22)
#19     Declarer.test.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test/src/backend/declarer.Dart:168:27)
<asynchronous suspension>
#20     Invoker.waitForOutstandingCallbacks.<anonymous closure> (package:test/src/backend/invoker.Dart:249:15)
<asynchronous suspension>
#25     Invoker.waitForOutstandingCallbacks (package:test/src/backend/invoker.Dart:246:5)
#26     Declarer.test.<anonymous closure>.<anonymous closure> (package:test/src/backend/declarer.Dart:166:33)
#31     Declarer.test.<anonymous closure> (package:test/src/backend/declarer.Dart:165:13)
<asynchronous suspension>
#32     Invoker._onRun.<anonymous closure>.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test/src/backend/invoker.Dart:403:25)
<asynchronous suspension>
#46     _Timer._runTimers (Dart:isolate/runtime/libtimer_impl.Dart:382:19)
#47     _Timer._handleMessage (Dart:isolate/runtime/libtimer_impl.Dart:416:5)
#48     _RawReceivePortImpl._handleMessage (Dart:isolate/runtime/libisolate_patch.Dart:169:12)
(elided 30 frames from class _FakeAsync, package Dart:async, and package stack_trace)

J'apprécierais vraiment toute sorte d'aide ou de rétroaction.

7
ssiddh

Essayez d'envelopper votre test avec

testWidgets('Navigate to landing page on correct login url',
    (WidgetTester tester) async {
    await tester.runAsync(() async {

      // test code here

    });
 });
8
Günter Zöchbauer