Testing
Last modified on Thu 21 Jan 2021

We have few options for testing of flutter apps: unit test (including bloc test), UI tests, end-to-end test.

Unit test should not have any dependecy on Flutter widgets because there is no actual canvas drawing happening. Widget tests have canvas drawing and they are used for UI and end-to-end tests.

These kind of tests shouldn't have any dependecy on platform (like MethodChannel or plugin package) because tests run without device and they will fail. All platform interaction should be wrapped into separate class and injected so they can be mocked in tests.

Unit testing

We mainly focus on unit testing the blocs because that's where the most of the bussines logic lies. You can test other parts, especially if they are logic-heavy, but here we'll just show how to test blocs.

We use bloc_test. You can read there details. Usually, with our architecture you want to mock all of the bloc dependencies and for that you can use Mockito. Here's example how you can write that:

    blocTest<LoginBloc, LoginEvent, LoginState>('Login is successful after makeRequest',
      build: () async {
        LoginRepositoryMock loginRepositoryMock = LoginRepositoryMock();
        when(loginRepositoryMock.login(any)).thenAnswer((_) async => ApiToken("abc"));
        return LoginBloc(loginRepository: loginRepositoryMock);
      },
      act: (bloc) async {
        bloc.add(MakeRequest('name', 'pass'));
      },
      expect: <dynamic>[isA<LoginLoadingState>(), isA<LoadedLoginState>()],
    );

Widget testing

The widget test work on a level of widget. You can inflate it with await tester.pumpWidget(YourWidget()) and then you have API to perform actions on widget (button click, enter text) and verify that everything is OK - like expecting what text should be shown, what color...

You will also need to wrap YourWidget with Material and Scaffold in order to pumpWidget.

  testWidgets('GenericError should show exception message', (WidgetTester tester) async {
    // Build our app and trigger a frame.
    await tester.pumpWidget(
        Material(
          child: Scaffold(
            body: GenericError(error: ErrorState(HttpException("No internet connection"), null)),
      ),
    ));

    // Verify that error is shown
    expect(find.text(" No internet connection"), findsOneWidget);
  });

It's simple to test if you have stateless widgets with all of it's dependencies exposed as constructor parameters. It's a bit trickier if widget is using Bloc state, because now you need to create and pump BlocProvider and provide the mock bloc and send some mock states through it. For me, it proved easiest to extends the Bloc with mock implementation. Then you can control which states to dispatch through the test to simulate various scenarios.

Mockito

We use Mockito to mock dependencies like repositories. Usually, bloc will have dependencies on various repositories and these can be easily mocked with Mockito.