Flutter: How to implement FlutterError.OnError correctly - dart

Can someone show me how to implement overriding flutter errors during widget test so I can check for my own custom errors.
I have seen snippets online mentioning this but all of my implementations fail
void main() {
testWidgets('throws an error when scanning unknown term types', (WidgetTester tester) async {
await tester.pumpWidget(injectTestWidget(new ItemScanScreen()));
await tester.enterText(find.byKey(new Key('term')), '');
await tester.tap(find.byIcon(Icons.send));
await tester.pump();
expect(
tester.takeException(),
isInstanceOf<UnrecognizedTermException>(),
reason: 'should have thrown an UnrecognizedTermException error but didn\'t',
);
});
}
the code above fails with the message below even though it looks like it in fact 'caught' my error:
The following UnrecognizedTermException was thrown running a test:
Instance of 'UnrecognizedTermException'
...
I read that you could do something like the snippet below but it did not see how/where to implement it:
final errorHandled = expectAsync0((){});
FlutterError.onError = (errorDetails) {
// handle error
errorHandled();
});

I use the code below in production to log errors to a server.
main.dart:
import 'dart:async';
import 'package:flutter/material.dart';
import 'logging.dart';
void main() async {
FlutterError.onError = (FlutterErrorDetails details) async {
new ErrorLogger().logError(details);
};
runZoned<Future<void>>(() async {
// Your App Here
runApp(MainApp());
}, onError: (error, stackTrace) {
new ErrorLogger().log(error, stackTrace);
});
}
logging.dart:
class ErrorLogger {
void logError(FlutterErrorDetails details) async {
//FlutterError.dumpErrorToConsole(details);
_sendToServer(details.exceptionAsString(), details.stack.toString());
}
void log(Object data, StackTrace stackTrace) async {
// print(data);
// print(stackTrace);
_sendToServer(data.toString(), stackTrace.toString());
}
void _sendToServer(String a, String b) async {
// Implementation here
}
}

This is was design for a test. I switched to wrapping logic in try/catch then running expect() on the "error message text" present concept. ex:
try {
throw new UnrecognizedTermException();
} catch (e) {
setState(() => _status = e.errMsg());
}
// then in my test
expect(find.text('Could not determine the type of item you scanned'), findsOneWidget);

Related

Dart Understanding Futures

when I run the below code snippet,I can't "Your order is: Large Latte".Instead I get "Your order is: Instance of '_Future".So what am I doing wrong?
Future<String> createOrderMessage() async{
var order = await fetchUserOrder();
return 'Your order is: $order';
}
Future<String> fetchUserOrder() =>
Future.delayed(
const Duration(seconds: 2),
() => 'Large Latte',
);
void main() {
print(createOrderMessage());
}
You have to await the first method call. So change to this:
void main() async {
print(await createOrderMessage());
}
If not, the main method will not wait for the Future to complete and instead just print that createOrderMessage() is an instance of a Future.

FUtureProvider was disposed before value was emitted

I wrote the following code and encountered the error The provider AutoDisposeFutureProvider<Data>#d1e31(465-0041) was disposed before a value was emitted.
I thought it was strange, so I debugged FutureProvider's onDispose and found that it was disposed during the await of the API call, which is confirmed by the output of disposed!
class HogeNotifier extends StateNotifier<Hoge> {
onFormSubmitted(String input) async {
final value = await _reader(searchProvider(input).future); // The provider AutoDisposeFutureProvider<Data>#d1e31(465-0041) was disposed before a value was emitted.
// execute by using value
}
}
final searchProvider =
FutureProvider.autoDispose.family<Data, String>((ref, value) async {
ref.onDispose(() {
print("disposed!");
});
try {
final result = await dataSource.find(keyword: value); //call api asynchronously
return Future.value(result);
} on Exception catch (e) {
return Future<Data>.error(e);
}
});
How can I solve this problem?

Why exception is not caught this way in async function in Dart?

Future<void> fetchUserOrder() async {
// Imagine that this function is fetching user info but encounters a bug
try {
return Future.delayed(const Duration(seconds: 2),
() => throw Exception('Logout failed: user ID is invalid'));
} catch(e) {
// Why exception is not caught here?
print(e);
}
}
void main() {
fetchUserOrder();
print('Fetching user order...');
}
It outputs
Fetching user order...
Uncaught Error: Exception: Logout failed: user ID is invalid
Which says the exception is not caught. But as you see, the throw Exception clause is surrounded by try catch.
The try-catch block will only catch exception of awaited Future. So you have to use await in your code:
Future<void> fetchUserOrder() async {
// Imagine that this function is fetching user info but encounters a bug
try {
return await Future.delayed(const Duration(seconds: 2),
() => throw Exception('Logout failed: user ID is invalid'));
} catch(e) {
// Why exception is not caught here?
print(e);
}
}
void main() {
fetchUserOrder();
print('Fetching user order...');
}

Uncaught Error: Unsupported operation: ProcessUtils._sleep

import 'dart:io';
void main() {
performTask();
}
void performTask() {
task1();
task2();
task3();
}
void task1() {
print('task1');
}
void task2() {
Duration timeDuration = Duration(seconds: 3);
sleep(timeDuration);
print('task2');
}
void task3() {
print('task3');
}
After executing first function that is task1() it throws an error:
Uncaught Error: Unsupported operation: ProcessUtils._sleep
I just hit the same roadblock! Not sure how to get sleep to work, but i found that using async/await is a bit more predictable:
// Unused import
// import 'dart:io'; // Delete me
void main() {
performTask();
}
// No need for async/await here, just the method in which it's used to await Future.delayed
void performTask() {
task1();
task2();
task3();
}
void task1() {
print('task1');
}
// I'm still a bit new to flutter, but as I understand it, best practice is to use Future<T> myFunction() {...} when defining async/await method.
// In this case <T> is <void> because you're not returning anything!
Future<void> task2() async {
Duration timeDuration = Duration(seconds: 3);
// sleep(timeDuration) // Delete Me
await Future.duration(timeDuration); // replacement for the sleep method, comes from the 'package:flutter/material.dart'
print('task2');
}
void task3() {
print('task3');
}
Credit: How can I "sleep" a Dart program
Since Future.duration doesn't
await Future.delayed(const Duration(seconds: 1));
credit: How can I "sleep" a Dart program

Flutter - Mockito behaves weird when trying to throw custom Exception

Trying to use Mockito to test my BLoC, the BLoC makes a server call using a repository class and the server call function is supposed to throw a custom exception if the user is not authenticated.
But when I am trying to stub the repository function to throw that custom exception, the test just fails with the following error:
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>
And this is what my BLoC code looks like: fetchNotifications function calls the repository function and handles the response and errors. There are two catchError blocks, one handles AuthorizationException case and other handles any other Exception. Handling AuthorizationException differently because it will be used to set the Login state of the application.
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");
}
}
This is what my repository code looks like:
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);
});
}
}
The function getNotificationsListFromServer is supposed to throw the AuthorizationException, which is supposed to be propagated through getNotificationList
This is the test case that is failing with the error mentioned before:
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"));
});
And this is what the custom exception looks like:
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: When I tested my repository class and the function throwing the custom exception those tests were passed.
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>()));
});
Above test passed and works as expected.
I am a new college grad working my first full time role and I might be doing something wrong. I will really appreciate any feedback or help, everything helps. Thanks for looking into this question.
You're using thenThrow to throw an exception, but because the mocked method returns a Future you should use thenAnswer.
The test would be like that:
test('getNotification observable gets error on AuthorizationException', () async {
// Using thenAnswer to throw an exception:
when(mockNotificationsRepository.getNotificationList())
.thenAnswer((_) async => throw AuthorizationException("test error", "test description"));
scheduleMicrotask(() => notificationBloc.fetchNotifications());
await expectLater(notificationBloc.getNotificationList, emitsError("Error"));
});
I think you are using the wrong TypeMatcher class. You need to use the one from the testing framework and not the one from the Flutter framework.
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>()));
});
}

Resources