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

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)

Related

How to get result of rootBundle.loadString(key) during flutter widget test?

I want to read a file from an asset during widget build with help of rootBundle but when I tried to call rootBundle.loadString('assets/myJsonAsset.json') like below :
testWidgets('load assets', (WidgetTester tester) async {
final result = await rootBundle.loadString('assets/myJsonAsset.json');
//above loadString method never return any result
expect(result, isA<String>());
});
then it causes the error :
Unable to load asset: assets/myJsonAsset.json
And the rootBundle.loadString('assets/myJsonAsset.json') never return any result.
Try to add this line to be executed before your tests:
TestWidgetsFlutterBinding.ensureInitialized();
This function will make your assets available for tests

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

Flutter Widget Test passes on device but not without one

I have a widget that displays a list of data from an api and I'm trying to write tests for it's various states starting with it's empty state.
Currently my test pumps the widget in question, the widget makes a network call which I'm mocking and then it checks to see if the empty state text is shown.
This test passes when I run the test on a device using
flutter run --flavor myTestApp -t test/booking/booking_list_widget_test.dart
But fails when I run it from the IDE (IntelliJ) the failure exception is:
The following TestFailure object was thrown running a test:
Expected: exactly one matching node in the widget tree
Actual: _TextFinder:<zero widgets with text "You're all caught up." (ignoring offstage widgets)>
Which: means none were found but one was expected
It seems to not be waiting for the Duration given to tester.pump and I've tried wrapping it in a tester.runAsync and using pump and settle etc but i cannot get it to pass.
Any ideas welcome I cant share the widget tree but I can share the test
void main() {
setupFirebaseAuthMocks();
setUpAll(
() async {
SharedPreferences.setMockInitialValues(
{
Constants.USER_ID: '1234567890',
},
);
},
);
testWidgets(
'emptyListTest',
(tester) async {
await _initializeDependencies(tester);
final dio = di.getIt.get<Dio>();
final dioAdapter = DioAdapter(dio: dio);
dioAdapter.onGet(
'/url/user/1234567890',
(server) => server.reply(
200,
BookingResponse(
(b) => b
..data = <Booking>[].toBuiltList().toBuilder()
..pagination = Pagination((b) => b
..last = ''
..first = ''
..next = ''
..skip = 0
..count = 0).toBuilder(),
),
),
);
final testApp = await tester.runAsync(
() => wordskiiTestApp(
widgetUnderTest: BookingView(),
),
);
await tester.pumpWidget(testApp!);
await tester.pump(const Duration(seconds: 1));
expect(find.text('AVAILABLE'), findsOneWidget);
// debugDumpApp();
expect(
find.text(
'You\'re all caught up.',
),
findsOneWidget,
);
},
);
}
Future<void> _initializeDependencies(WidgetTester tester) async {
await tester.runAsync(di.getIt.reset);
await tester.runAsync(di.initTesting);
await tester.runAsync(di.getIt.allReady);
}
On which Widget is 'You\'re all caught up' expected? I had similar issues encountered previously when I tried find.textContaining() on a ListView item. I was able to solve my issue by finding out which widget the Text should appear.
On mine, I had to use find.widgetWithText(InkWell, String). To find out the which Widget it is, you can tap on the widget displayed on screen when you've run the test (i.e. on an emulator). The logs should display the tapped Widget.

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?

(Flutter/Dart) Two async methods in initState not working

I have a function in my initState:
#override
void initState() {
_fetchListItems();
super.initState();
}
This function is very simple. It has two async await operations of sqflite, one of which waits for the other to complete in it:
_fetchListItems() async {
wait() async {
number = await db.getNumber(userId); }
await wait();
List rawFavouriteList = await db.getList(number);
setState((){
rawFavouriteList.forEach((item){
_favouriteList.add(Model.map(item));
}});
}
I have to wait for number to be fetched, only then I can fetch serialized List, which then I deserialize and populate then add to the List which is displayed in ListView.
The problem is, this is not working in initState. It's working fine when it's called through onPressed() of a button, but not in initState.
Points to Note:
1) There is no error being thrown
2) I have already tried more conservative alternatives by using await for even rawFavouriteListthrough a separate function like wait() before using setState() top populate the _favouriteList, even though it's working fine manually through buttons.
3) Everything works fine if I manually enter number value in the second database query and just remove the first query, i.e.
_fetchListItems() async {
List rawFavouriteList = await db.getList(42);
setState((){
rawFavouriteList.forEach((item){
_favouriteList.add(Model.map(item));
}});
}