I am trying to perform a widget test, specifically navigation test. I am using bloc architecture, setting a stream on the bloc triggers a series of events inside the bloc, gets session info from the server call (which returns a future of session info object), on successful server call a login stream is set and the widget has a stream subscription to this stream and navigates to the next screen.
I am using mockito to mock the server call and stubbing the server call to return a future of success response. The problem is the when I am calling pumpAndSettle()
it is getting timed out as it is not waiting for the future to complete and return the success response.
I apologize if I am not making it very clear, but here is the sample 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);
});
});
On running the widget test the tester.pumpAndSettle()
inside testWidgets
times out before the future is completed. This is the error log:
══╡ 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)
I would really appreciate any kind of help or feedback.
There are two ways to test timer precision in flutter widgets. First, you can use an action button, like FloatingActionButton, which will update if the counter changes. Second, you can manually run the widget tests.
There are three types of tests that Flutter supports. A unit test verifies the behavior of a method or class. A widget test verifies the behavior of Flutter widgets without running the app itself. An integration test (also called end-to-end testing or GUI testing) runs the full app.
Try wrapping your test with
testWidgets('Navigate to landing page on correct login url',
(WidgetTester tester) async {
await tester.runAsync(() async {
// test code here
});
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With