bloc icon indicating copy to clipboard operation
bloc copied to clipboard

test: Missing example integration_test that works for HydratedBloc

Open RickPoleshuck opened this issue 2 years ago • 8 comments

Our integration test is failing with this error:

Error resuming isolate isolates/3587623678467603: 106 Isolate must be paused
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following StorageNotFound was thrown building _InheritedProviderScope<UserBloc?>(value: <not yet
loaded>):
Storage was accessed before it was initialized.
Please ensure that storage has been initialized.

For example:

final storage = await HydratedStorage.build();
HydratedBlocOverrides.runZoned(
  () => runApp(MyApp()),
  storage: storage,
);

But the first line of the integration test calls app.main() which has this code:

await runZonedGuarded(
    () async {
      HydratedBlocOverrides.runZoned(
        () => runApp(App()),
        storage: storage,
      );
    },

RickPoleshuck avatar May 06 '22 17:05 RickPoleshuck

Hi @RickPoleshuck 👋 Thanks for opening an issue!

Unfortunately this is due to https://github.com/flutter/flutter/issues/96939. I'm trying to work with the Flutter team to resolve it but it's an issue with how the integration_test package does not respect zone values. Sorry for the inconvenience!

felangel avatar May 07 '22 04:05 felangel

How are people working around the inability to use integration_test? It is hard to believe that with all the developrs using HydratedBloc, that nobody has come up with a work around so that they can do an integration test. I want to use an integration_test to automate the generation of screenshots for dozens of screens on 10 different emulators, 5 iOS and 5 Android.

In order to get an integration test working on iOS for provider, I had to patch IntegrationTestPlugin.m. I really would like the flutter team to pay more attention to integration_test. :-)

Thanks for a great product @felangel.

RickPoleshuck avatar May 08 '22 15:05 RickPoleshuck

How are people working around the inability to use integration_test? It is hard to believe that with all the developrs using HydratedBloc, that nobody has come up with a work around so that they can do an integration test. I want to use an integration_test to automate the generation of screenshots for dozens of screens on 10 different emulators, 5 iOS and 5 Android.

In order to get an integration test working on iOS for provider, I had to patch IntegrationTestPlugin.m. I really would like the flutter team to pay more attention to integration_test. :-)

Thanks for a great product @felangel.

I completely agree this is a huge issue and I was hoping the Flutter team would agree and get a fix in. Instead, I’ve had many conversations with members of the team regarding why developers should avoid using zones in Flutter. I’m planning to give them a few more days to respond to my most recent comment and if there’s no progress I think we’ll be forced to move away from zones and probably go back to a static Bloc.observer and HydratedBloc.storage mechanism (like what existed before v8.0.0).

I’m really sorry for the inconvenience and wish this could have been fixed in the framework. Open to any other ideas/suggestions if you have them.

felangel avatar May 08 '22 15:05 felangel

@felangel Thank you very much for putting effort to try to fix the problem 🙏

It's also impacting us. I use hydrated_bloc in almost every app I work on and this makes it impossible to do integration testing.

Personally, I was fine with static Bloc.observer and HydratedBloc.storage.

bartekpacia avatar May 17 '22 13:05 bartekpacia

We are able to do integration testing, but not automatically. :-) We have a spreadsheet with tests that someone has to run through one at a time before a release. It is costly but it works.

On Tue, 2022-05-17 at 06:42 -0700, Bartek Pacia wrote:

@felangel Thank you very much for putting effort to try to fix the problem 🙏test It's also impacting us. I use hydrated_bloc in almost every app I work on and this makes it impossible to do integration testing. Personally, I was fine with static Bloc.observer and HydratedBloc.storage. — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

RickPoleshuck avatar May 17 '22 14:05 RickPoleshuck

I hope this will be resolved soon in Flutter (see discussion in https://github.com/flutter/flutter/issues/96939).

felangel avatar May 17 '22 14:05 felangel

I managed to get a screenshot integration test to work using mock blocs and what I'm calling a "middle to end" test rather than an "end to end" test.

` void takeScreenshot(binding, screenshotDir, name) async { var screenshotBytes = await binding.takeScreenshot(name);

if (Platform.isIOS) { final path = '$screenshotDir/$name.png'; final File image = File(path); image.writeAsBytesSync(screenshotBytes); } else if (Platform.isAndroid) { var msg = jsonEncode({'name': name, 'image': screenshotBytes}); final socket = await Socket.connect('10.0.2.2', 3013); socket.write(msg); socket.close(); } }

void main() { final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized() as IntegrationTestWidgetsFlutterBinding; String platform = Platform.isAndroid ? 'android' : 'ios';

testWidgets('screenshot', (WidgetTester tester) async { UserBloc userBloc = MockUserBloc(); mocktail.when(() => userBloc.state).thenReturn(const UserState());

SettingsCubit settingsCubit = MockSettingsCubit();
mocktail.when(() => settingsCubit.state).thenReturn(const SettingsState());

DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
String emulatorName = 'unknown';
if (Platform.isAndroid) {
  // This is required prior to taking the screenshot (Android only).
  await binding.convertFlutterSurfaceToImage();

  AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
  emulatorName = androidInfo.model.toString();
} else {
  IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
  emulatorName = iosInfo.name!;
}
final screenshotDir = '/tmp/screenshots/$platform/$emulatorName';
if (Platform.isIOS) Directory(screenshotDir).createSync(recursive: true);

await tester.pumpWidget(
  MultiBlocProvider(
    providers: [
      BlocProvider<UserBloc>(
        create: (context) => userBloc,
      ),
      BlocProvider<SettingsCubit>(
        create: (context) => settingsCubit,
      ),
    ],
    child: MaterialApp(
      theme: themeData,
      onGenerateRoute: (_) => SplashPage.route(),
      home: UserSelectionPage(),
    ),
  ),
);

await tester.pumpAndSettle(); // one pump for native splash
await tester.pumpAndSettle(); // one pump for app
takeScreenshot(binding, screenshotDir, '01_user_type');

await tester.tap(find.byKey(const Key('user_type.student')));
await tester.pumpAndSettle(const Duration(seconds: 1));
takeScreenshot(binding, screenshotDir, '02_register_screen');

await tester.enterText(find.byKey(const Key('register.firstName')), 'John');
await tester.pumpAndSettle(const Duration(seconds: 1));
await tester.enterText(find.byKey(const Key('register.lastName')), 'Doe');
await tester.pumpAndSettle(const Duration(seconds: 1));
await tester.enterText(
  find.byKey(const Key('register.email')),
  '[email protected]',
);

await tester.drag(find.byType(RegistrationPage), const Offset(0, 500));
await tester.pumpAndSettle(const Duration(seconds: 1));
await tester.enterText(
  find.byKey(const Key('register.password')),
  'password123',
);
await tester.pumpAndSettle(const Duration(seconds: 1));
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pumpAndSettle(const Duration(seconds: 1));

await tester.drag(find.byType(RegistrationPage), const Offset(0, 500));
await tester.pumpAndSettle(const Duration(seconds: 1));
await tester.enterText(
  find.byKey(const Key('register.confirm')),
  'password123',
);
await tester.pumpAndSettle(const Duration(seconds: 1));

await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pumpAndSettle(const Duration(seconds: 1));

await tester.drag(find.byType(RegistrationPage), const Offset(0, 500));
await tester.pumpAndSettle(const Duration(seconds: 1));
await tester.tap(find.byKey(const Key('terms')));
await tester.pumpAndSettle(const Duration(seconds: 1));

takeScreenshot(binding, screenshotDir, '03_filled_register_screen');
await Future.delayed(const Duration(seconds: 5));

}); } `

Spoleshuck avatar May 29 '22 17:05 Spoleshuck

Hi @Spoleshuck 👋 Thanks for sharing your solution.

Using a mock cubit/bloc definitely works but it’s not really a solution imo because it’s no longer a proper integration test. In addition, not all blocs/cubits are top level so it’s not very ergonomic to manually inject mocks for every bloc/cubit instance in e2e tests.

In my opinion, the proper solution is to wait for https://github.com/flutter/flutter/issues/96939 to be resolved since this is a bug in the framework.

felangel avatar May 29 '22 18:05 felangel

@RickPoleshuck this has finally been resolved in hydrated_bloc v9.0.0. Closing for now but feel free to comment if you have any issues.

felangel avatar Nov 04 '22 04:11 felangel