Flutter widget test exception not being handled properly - flutter

I do not believe this is a duplicate of this SO issue because the stated solution does not work in my case.
I am not able to properly catch / verify an Exception in my widgetTest and I am not sure why. Instead of handling the Exception, the test actually just fails on the Exception that I am trying to test.
Here is my code:
child: ElevatedButton(
onPressed: () {
try {
bool loggedIn = await widget.authHandler.login(email, password);
} on InvalidStateException catch (e) {
setState(() {
errorMessage = 'Error Logging In. Please Try Again';
loginError = true;
});
}
})
Here is my test:
testWidgets('Testing Login button Failure - Exception Thrown',
(tester) async {
final amplifyAuthMock = MockAmplifyAuth();
when(amplifyAuthMock.login('test#test.com', 'password!'))
.thenThrow(InvalidStateException);
await tester.pumpWidget(createLoginForm(amplifyAuthMock));
await inputDummyLoginText(tester);
await tester.tap(find.byType(ElevatedButton));
expect(tester.takeException(), isInstanceOf<InvalidStateException>());
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.text('Error Logging In. Please Try Again'), findsOneWidget);
});
When the test is run, this is the error I get:
The following TestFailure object was thrown running a test (but after the test had completed):
Expected: <Instance of 'InvalidStateException'>
Actual: <null>
Which: is not an instance of 'InvalidStateException'
and if I change the test to be
expect(() async => await tester.tap(find.byType(SkillTreeElevatedButton)), throwsA(isA<InvalidStateException>()));
Then I still get
The following TestFailure object was thrown running a test (but after the test had completed):
Expected: throws <Instance of 'InvalidStateException'>
Actual: <Closure: () => Future<void>>
Which: returned a Future that emitted <null>
I was thinking that since the function I am mocking returns the exception, and that is caught in the onPressed widget code, that I wouldn't have any problems. Best case scenario, I can catch the occurance of the exception and verify it happened, and worst case I could at least just see that my error text was added to the page... but I'm just getting this error. Am I going about handling errors wrong in Flutter?

Related

How to unit test Flutter's StreamSubscription onData?

This is what I tried so far.
Let's say result is a StreamSubscription.
This is my flutter file
try {
result.listen(
(event) async {
// This converts the JSON data
final news = NewsModel.fromJSON(jsonDecode(event));
// This saves the data to local database
await localDataSource.saveNews([news]);
},
onError: (e) {
debugPrint('$e');
},
);
} catch (e) {
debugPrint('$e');
}
this is my flutter test since I want to test if method localDataSource.saveNews() fails
await newsRepository.subscribe(); calls the try catch above
controller is a StreamController to add new data to the stream
news is a dummy data, it doesn't matter because whatever the localDataSource do it will throw a LocalDBException
also I am using Mockito https://pub.dev/packages/mockito to mock the localDataSource above
test(
'should handle fail save news method',
() async {
// arrange
when(mockLocalDataSource.saveNews(any)).thenThrow(LocalDBException());
// act
await newsRepository.subscribe();
controller.add(news)
// assert
},
);
As you can see I don't have any condition to pass the flutter test, but that's beyond the point as this flutter test already breaks the stream even if I have a onError on my listener.
if I use controller.addError(LocalDBException()) the onError works, but if I deliberately throw an exception from the method localDataSource.saveNews() it breaks the stream.
Given this context I want to know 2 things:
I want to know how to handle the error inside the onData of StreamSubscription, if a method / function throw an exception, as it ignores the onError if a method / function throw an exception inside the listener.
Is adding an error through addError() function the same as throwing an exception inside the stream?

Issues handling Navigation when exception occurs [Bloc]

I'm creating a flutter app using bloc/cubit pattern. I'm having issues preventing navigation when an exception occurs, here's my button onTap function:
function: () async {
if (state.termsAndConditionsAccepted &&
state.status.isValid &&
!state.status.isPure) {
await context
.read<SignUpCubit>()
.onClickSignUp()
.then(((value) => {Navigator.pop(context)}));
}
and here's the logic of sign in
Future<void> onClickSignUp() async {
try {
// sign in logic...
// Provoking an exception...
throw Exception('An error ocrred. Please try again.');
} on Exception catch (exception) {
String exMessage = exception.toString();
log(exMessage);
// This emits a new state with the error and shows an error snackbar based on this new state
emit(state.copyWith(
status: FormzStatus.submissionFailure,
exceptionErrorMessage: exMessage));
}
As far as I know, the .then() callback function will only be executed if the Future function completes successfully(onClickSignUp()) but the Navigation occurs even if the exception is thrown.
I managed make it work by returning a boolean value to the then() callback and do the navigation depending in that value(if true navigate/if false do nothing), but that would require me to return true when function completes successfully and return false in all my catch blocks which does not seems a good practice.
Since I'm handling the states from my cubit class I cannot use .onError or .catchError() callbacks on the button's function.
Hopefully someone can suggest a better error handling in this case or what could be an appropriate error handling for bloc pattern

Test Execution stops when code throws Excetion - I have to press F5 again to resume - Everything seems to work - But am I executing my tests correct?

I am running tests against a Flutter app, things are being mocked with mocktail.dart. Testing frameworks in use are bloc_test.dart and flutter_test.dart.
By the way the code is from Felix Angelov's bloc repository:
https://github.com/felangel/bloc/blob/master/examples/flutter_infinite_list/test/posts/bloc/post_bloc_test.dart
Normally when I press F5, while having a test file visible in VS Code, my tests just executes and I get this comfortable green of all the tests passed.
But when a test includes an exception thrown in the code, VS Code stops the execution of the tests, and I have to press F5 to continue.
It seems to me that both the code and the test works as they should, and the test also passes after I press F5 to resume the execution of the tests.
But my life would be a little more comfortable if I did not have to press F5 to resume the test suite when an Exception is being tested.
Am I executing my test in the right way?
Is it perfectly normal, that I have to press F5 again, to resume the tests, after an exception has been thrown?
This is how VS Code looks like when the test execution stops due to an exception thrown:
Code under test looks something like this:
Future<PostState> _mapPostFetchedToState(PostState state) async {
try {
...
final posts = await _fetchPosts(state.posts.length);
...
} on Exception {
return state.copyWith(status: PostStatus.failure);
}
}
Future<List<Post>> _fetchPosts([int startIndex = 0]) async {
throw Exception('error fetching posts');
}
Test code looks like this:
blocTest<PostBloc, PostState>(
'emits failure status when http fetches posts and throw exception',
build: () {
when(() => httpClient.get(any())).thenAnswer(
(invocation) async => http.Response('', 500),
);
return PostBloc(httpClient: httpClient);
},
wait: const Duration(microseconds: 500),
act: (bloc) => bloc.add(PostFetched()),
expect: () =>
<PostState>[const PostState(status: PostStatus.failure)],
verify: (_) {
verify(() => httpClient.get(_postsUrl(start: 0))).called(1);
});

Flutter, testing future that throws an Error makes the test unloadable

I'm trying to run a flutter test where a widget displays an error page when the Future provided to it throws an error (via FutureBuilder).
However the line where I create the future seems to be making the test fail.
final futureError = Future.delayed(Duration(milliseconds: 20))
.then((value) => throw Error());
with the message
Failed to load "D:\Projects\flutter\....dart": Instance of 'Error'
Putting it inside the test function resolved the issue
testWidgets('...',
(WidgetTester tester) async {
await tester.runAsync(() async {
final futureError = Future.error('error');
// ...
(it was in the group method prior to this)

How do you test if an exception is thrown in Flutter?

I seem to be unable to test if an exception is thrown in Flutter. I would expect the test to pass if an exception is thrown considering I expect an Exception.
What I have tried:
import 'package:flutter_test/flutter_test.dart';
void main() {
test('Range error test', () {
expect(throw RangeError(""), throwsA(RangeError("")));
});
test('Range error test', () {
expect(throw RangeError(""), throwsA(RangeError));
});
test('Range error test', () {
expect(throw RangeError(""), throwsRangeError);
});
test('ConcurrentModificationError error test', () {
expect(throw ConcurrentModificationError(""), throwsA(ConcurrentModificationError));
});
test('NumberFormat error test', () {
expect(int.parse("sdffg"), throwsA(FormatException));
});
test('NumberFormat error test', () {
expect(int.parse("sdffg"), throwsFormatException);
});
test('Range error test', () {
var list = [];
expect(list[1], throwsRangeError);
});
test('Range error test', () {
var list = [];
expect(list[1], throwsA(RangeError));
});
}
dependencies in pubspec.yaml:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
test:
Errors after running them in Android Studio (I get the same errors when running from cli):
Let's take one of your examples:
expect(throw RangeError(""), throwsA(RangeError("")));
There are a few problems here.
Since throw RangeError("") is part of a larger expression, it is evaluated before the whole expression does, so you can't hope for expect() to be evaluated if throw RangeError("") breaks the flow.
To make this explanation more didactic, imagine we extracted the arguments into variables:
final neverAssigned = throw RangeError("");
final throwsRangeError = throwsA(RangeError(""));
expect(neverAssigned, throwsRangeError);
As you may have realized, we never reach expect() because the program already throws at the first line.
To solve this problem, expect() requires you to pass a function that potentially throws. It will check that it is a function, wrap the call in try-catch and check whether the error thrown is what you expect.
We should end up with something like this:
expect(() => throw RangeError(""), throwsA(RangeError("")));
However, this doesn't work as well, since errors in general don't implement operator == and hashCode, so RangeError("") == RangeError("") gives you false. This means it's not that simple to check whether the error thrown is exactly what you expect.
Instead, you should check only the type with something like throwsA(isA<RangeError>()), or check the message with having.