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.
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
});
});