How to implement widget tests by using MockBloc?
Asked Answered
I

1

7

I'm trying to implement a Widget Test in order to test a login form. This test depends on a bloc which I'm mocking by using MockBloc. However, it throws the following error:

══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK╞════════════════════════════════════════════════════
The following StateError was thrown running a test:
Bad state: No method stub was called from within `when()`. Was a real method called, or perhaps an 
extension method?

I found a similar error in the following link, but I do not see how that can help me to solve my problem.

I also looked at the following file on gitlub, which is an example of a widget test by using bloc_test. The link can be found on the official website of the Bloc Library - specifically in Todos App in Flutter using the Bloc library.

However, that example is using bloc_test: ^3.0.1 while I'm using bloc_test: ^8.0.0, which can be found here.

Here is a minimal example:

  • LoginForm Widget
class LoginForm extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Form(
      key: '_loginForm',
      child: Column(
        children: <Widget>[
           ...
           BlocConsumer<AuthenticationBloc, AuthenticationState>(
             listener: (context, state) {
               ...
             },
             builder: (context, state) {
               if (state is AuthenticationInitial) {
                 ...
               } else if (state is LoggingIn || state is LoggedIn) {
                 ...
               } else if (state is Error) { 
                 return Column(
                  children: <Widget>[
                    ...
                    Message(
                      message: state.message,
                      messageContainerWidth: 290,
                      messageContainerHeight: 51,
                    ),
                    ...
                  ],
                );
               }
             }
           ),
        ],
      ),
    );
  }
}
  • Message Widget
class Message extends StatelessWidget {
  final String message;
  final double messageContainerWidth;
  final double messageContainerHeight;

  ...
  @override
  Widget build(BuildContext context) {
    return Container(
      width: messageContainerWidth,
      height: messageContainerHeight,
      child: Center(
        child: message != ""
            ? Text(
                message,
                textAlign: TextAlign.center,
                style: TextStyle(
                  color: Color.fromRGBO(242, 241, 240, 1),
                  fontSize: 15,
                ),
              )
            : child,
      ),
    );
  }
}
  • Widget Test (I want to test that a Message is shown when the Authentication state is Error)
...
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
...

// Mocking my LoginUser usecase
class MockLoginUser extends Mock implements LoginUser {}

// Mocking my bloc
class MockAuthenticationBloc
    extends MockBloc<AuthenticationEvent, AuthenticationState>
    implements AuthenticationBloc {}

class AuthenticationStateFake extends Fake implements AuthenticationState {}

void main() {
  MockLoginUser mockLoginUser;

  setUpAll(() {
    registerFallbackValue<AuthenticationState>(AuthenticationStateFake());
  });

  setUp(() {
    mockLoginUser = MockLoginUser();
    authenticationBloc = AuthenticationBloc(loginUser: mockLoginUser);
  });

  group('Login', () {
    testWidgets(
        'should show a Message when the Authentication state is Error',
        (WidgetTester tester) async {
      whenListen(
        authenticationBloc,
        Stream.fromIterable(
          [
            LoggingIn(),
            Error(
              message: 'Some error message',
            ),
          ],
        ),
        initialState: AuthenticationInitial(),
      );
     
      final widget = LoginForm();
      await tester.pumpWidget(
         BlocProvider<AuthenticationBloc>(
          create: (context) => authenticationBloc,
          child: MaterialApp(
            title: 'Widget Test',
            home: Scaffold(body: widget),
          ),
        ),
      );
      await tester.pumpAndSettle();

      final messageWidget = find.byType(Message);
      expect(messageWidget, findsOneWidget);
    });
  });
}

I will really appreciate it if someone can help me to solve the error, or can let me know another way to implement the widget tests.

Thanks in advance!

Iatrogenic answered 16/4, 2021 at 13:16 Comment(0)
I
15

I solved the problem, I would like to share the answer, in case someone finds out the same problem.

First of all, this link was really helpful.

The solution was to change the Widget Test in the following way:

...
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
...

class MockAuthenticationBloc
    extends MockBloc<AuthenticationEvent, AuthenticationState>
    implements AuthenticationBloc {}

class AuthenticationStateFake extends Fake implements AuthenticationState {}

class AuthenticationEventFake extends Fake implements AuthenticationEvent {}

void main() {
  group('Login', () {

    setUpAll(() {
      registerFallbackValue<AuthenticationState>(AuthenticationStateFake());
      registerFallbackValue<AuthenticationEvent>(AuthenticationEventFake());
    });

    testWidgets(
        'should show a Message when the Authentication state is Error',
        (WidgetTester tester) async {
      // arrange
      final mockAuthenticationBloc = MockAuthenticationBloc();
      when(() => mockAuthenticationBloc.state).thenReturn(
        LoggingIn(), // the desired state
      );

      // find
      final widget = LoginForm();
      final messageWidget = find.byType(Message);

      // test
      await tester.pumpWidget(
         BlocProvider<AuthenticationBloc>(
          create: (context) => mockAuthenticationBloc,
          child: MaterialApp(
            title: 'Widget Test',
            home: Scaffold(body: widget),
          ),
        ),
      );
      await tester.pumpAndSettle();

      // expect
      expect(messageWidget, findsOneWidget);
    });
  });
}
Iatrogenic answered 23/4, 2021 at 15:17 Comment(2)
A clarification would be great... What is the purpose of Mocking fake states and fake events if you are not using them ?Serration
Long time coming after this, but I believe the mocking of states and events is necessary if your widget you're pumping (in this case it's a LoginForm) utilizes those states and events. Otherwise, you're getting back real states, not the mocked ones, and when needs mocked ones.Woollen

© 2022 - 2024 — McMap. All rights reserved.