How do you test if an exception is thrown in Flutter? - 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.

Related

Flutter tests - Having two Integration tests in a same group ()

I'm trying to run two tests in a same group in one test file.
If I split them into two separate test files, they are working fine.
But if I add them in a group, the 2nd test fail with the error saying "TypeAdapter is already registered for the given id".
My app uses HiveDB and it sets up Hive boxes in the main.dart before launching the App. I understand that the 2nd test is also trying the same and fails because the setup is already done.
I followed this doc to setup and write integration tests.
Most of the youtube tutorials, medium articles, and other online resources explain so well on how to run one single test in a test file and they're outdated. I never found a resource having two tests in the same file yet.
Here's my main.dart
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
await HiveDB.setup();
runApp(App());
}
test_driver/integration_test.dart
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() => integrationDriver();
test/app_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:integrationtestdemo/main.dart' as app;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Home screen tests', () {
tearDown(() async {
print("TearDown() -- called");
await HiveDB.close();
});
testWidgets('Scenario # - loreum ipsum ...', (tester) async {
// ARRANGE
await app.main();
await tester.pumpAndSettle();
// ACT
// ASSERT
expect(1 + 3, 4);
});
testWidgets('Scenario #2', (tester) async {
// ARRANGE
await app.main();
await tester.pumpAndSettle();
// ACT
// ASSERT
expect(2 + 2, 4);
});
});
}
The command I use to execute tests:
flutter drive --driver=.\test_driver\integration_test.dart --target=.\test\app_test.dart
Error I get:
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞═════════════════
The following HiveError was thrown running a test:
There is already a TypeAdapter for typeId 1.
When the exception was thrown, this was the stack:
#0 TypeRegistryImpl.registerAdapter (package:hive/src/registry/type_registry_impl.dart:104:11)
#1 HiveDB.setup (package:occasionly/src/services/hive_db.dart:80:10)
<asynchronous suspension>
Could someone please help me here? I'm stuck on this for a long time and couldn't figure out a way to isolate the state between tests?
I have found a dirty trick to solve "TypeAdapter is already registered for the given id" this error
try to put it like this, and the error will not appear anymore
Hive.registerAdapter(YourAdapter(), override: true);
But I have no idea what is the possible impact could this argument do
I also faced the same issue, but I found a workaround by referring to this ERROR in flutter: widget_test.dart cannot detect MyApp(), you could try it.
testWidgets('Scenario #1', (tester) async {
// ARRANGE
await app.main();
...
});
testWidgets('Scenario #2', (tester) async {
// ARRANGE
await tester.pumpWidget(MyApp());
});

Handeling and reporting errors that thrown inside runZonedGuarded callback [Crashlytics, Flutter]

I am implementing Crashlytics with flutter,
and following this docs Using Firebase Crashlytics
I was wondering what would actually happen if an error is thrown inside runZonedGuarded's
error callback: (see the arrow in the code)
void main() async {
runZonedGuarded<Future<void>>(() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;
runApp(MyApp());
}, (error, stackTrace) async {
***** some buggy code here ******. <----------
await crashlytics.handleNonFlutterErrors(error,stackTrace);
});;
}
so I have tried it, and to my surprise it is still swallowed by flutter framework and stacktrace was dumped to console - I was expecting the app to crash but it didn't...
does any-body knows how to handle this types of errors?

Flutter widget test exception not being handled properly

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?

Unit tests running in parallel in flutter when using GetIt

I am writing an flutter App and I am using GetIt for dependency injection.
When I write unit tests, I do something like this:
void main() {
group("tests", () {
test("test1", () {
GetIt.I.reset();
GetIt.I.registerSingleton(MyDependency());
// Some code using GetIt.I<MyDependency>()
});
test("test2", () {
GetIt.I.reset();
GetIt.I.registerSingleton(MyDependency());
// Some code using GetIt.I<MyDependency>()
});
});
}
When I run the tests one after the other, it works perfectly.
When I run all test at once, I get errors on the line GetIt.I<MyDependency>()... like this:
'package:get_it/get_it_impl.dart': Failed assertion: line 372 pos 7: 'instanceFactory != null': Object/factory with type HttpActions is not registered inside GetIt.
So my guess is, that the tests run in parallel and the setup of one test with GetIt.I.reset() conflicts with the setup of the other test.
So how can I solve this?
Can I somehow tell the tests, that they just cannot run in parallel?
Or is there some way of safely using GetIt in this context?
I think your issue is not about running your tests in parallel but related to the fact that GetIt.I.reset() is an asynchronous method:
Implementation
Future<void> reset({bool dispose = true});
And as mentionned in the documentation of the package for this method:
[...] As dispose functions can be async, you should await this function.
Code Sample
I've made a new test sample based on the code you've provided and inspired by the unit tests written for the package and it seems to be working properly.
import 'package:flutter_test/flutter_test.dart';
import 'package:get_it/get_it.dart';
import 'package:so_tests/my_dependency.dart';
void main() {
// This method provided by `flutter_test` is called before each test.
setUp(() async {
// Make sure the instance is cleared before each test.
await GetIt.I.reset();
});
group('tests', () {
test('test1', () {
final getIt = GetIt.instance;
getIt.registerSingleton(MyDependency());
// Some code using getIt<MyDependency>()
});
test('test2', () {
final getIt = GetIt.instance;
getIt.registerSingleton(MyDependency());
// Some code using getIt<MyDependency>()
});
});
}

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)