Injecting generated mock classes into test" environment

556

After some thinking and a bit of hacking I came up with a workaround. It doesn't seem like anyone else has any better ideas so if someone else has this problem, then this is what I'm doing now:

The TLDR; is that I use @GenerateMocks([IAuthRepository]) inside my test env and then extend MockIAuthRepository instead of with mock and finally I have to cast the getIt instance with MockIAuthRepository in the test.

file structure

├── lib
│   └── infrastructure
│       └── auth
│           ├── firebase_repository.dart
│           ├── mock_firebase_repository.dart
│           └── mock_firebase_repository.mocks.dart
└── test
    └── presentation
        └── auth
            └── login_form_test.dart

mock_firebase_repository.dart

@Environment("test")
@LazySingleton(as: IAuthRepository)
@GenerateMocks([IAuthRepository])
class MockFirebaseAuthRepository extends MockIAuthRepository implements IAuthRepository {}

login_form_test.dart

void main() {
  setUpAll(() async {
    configureInjection(Environment.test);
  });

  testWidgets(
        'GIVEN form is filled in correctly '
        'WHEN LoginButon is pressed '
        'THEN see no errors', (tester) async {
      // Arrange
      final mock = getIt.get<IAuthRepository>() as MockIAuthRepository;  <-- here is magic!
      when(mock.signInWithEmailAndPassword(
        email: anyNamed("email"),
        password: anyNamed("password"),
      )).thenAnswer((_) async => const Right(unit));
      await tester.pumpWidget(
        MaterialApp(
          localizationsDelegates: const [
            Localize.delegate,
            GlobalMaterialLocalizations.delegate,
            GlobalWidgetsLocalizations.delegate,
            GlobalCupertinoLocalizations.delegate,
          ],
          supportedLocales: Localize.delegate.supportedLocales,
          home: BlocProvider(
            create: (context) => getIt<LoginFormBloc>(),
            child: const Scaffold(
              body: LoginForm(),
            ),
          ),
        ),
      );
      final Finder emailField = find.byKey(keyAuthEmailField);
      final Finder passwordField = find.byKey(keyAuthPasswordField);
      final Finder loginButton = find.byKey(keyAuthLoginButton);
      // Act
      await tester.pumpAndSettle();

      await tester.enterText(emailField, "[email protected]");
      await tester.enterText(passwordField, "Passw0rd");
      await tester.pumpAndSettle();

      await tester.tap(loginButton);
      await tester.pump(const Duration(seconds: 1));
      // Assert
      List<TextField> formFields = [];
      find.byType(TextField).evaluate().toList().forEach((element) {
        formFields.add(element.widget as TextField);
      });
      for (var element in formFields) {
        expect(element.decoration?.errorText, isNull);
      }
      expect(find.byKey(keyAuthErrorSnackbar), findsNothing);
      expect(find.byKey(keyAuthSuccessSnackbar), findsOneWidget);
    });
}

This isn't the prettiest solution as I don't think I should need to cast the type for MockIAuthRepository, but it works. I end up with the generated Mock in my infrastructure along with the mock injection and prod injection so there is some extra boilerplate, but at least I don't need to touch the mocks once they're setup and can swap them in quite easily when doing tests.

Hope this helps someone else and saves them from smashing their head against the keyboard for a week.

Share:
556
Lloyd Richards
Author by

Lloyd Richards

Updated on January 01, 2023

Comments

  • Lloyd Richards
    Lloyd Richards 10 months

    Im currently trying to build widget tests for a project (focusing on TDD). I'm using Injectable to generate the dependency injection as well as the environments to swap between various implementations. The problem I'm having is that I'm not able to use the @GenerateMocks([IAuthRepository]) like in my unit tests and so need to inject the mock into GetIt.

    @Environment("test")
    @LazySingleton(as: IAuthRepository)
    class MockFirebaseAuthFascade with Mock implements IAuthRepository {}
    

    In my widget test I first inject with the text env and then use getIt<IAuthRepository>() to call the mocked instance so I can use when and verify in my test:

    void main() {
      setUpAll(() async {
        configureInjection(Environment.test);
      });
    
    testWidgets(
            'GIVEN form is initial state '
            'WHEN ElevatedButton is pressed '
            'THEN see two error messaged below TextField', (tester) async {
          // Arrange
          await tester.pumpWidget(
            MaterialApp(
              localizationsDelegates: const [
                Localize.delegate,
                GlobalMaterialLocalizations.delegate,
                GlobalWidgetsLocalizations.delegate,
                GlobalCupertinoLocalizations.delegate,
              ],
              supportedLocales: Localize.delegate.supportedLocales,
              home: BlocProvider(
                create: (context) => getIt<SignInFormBloc>(),
                child: const Scaffold(
                  body: SignInForm(),
                ),
              ),
            ),
          );
          final mockAuth = getIt<IAuthRepository>();
          final Finder signIn =
              find.byKey(const Key("auth_sign_in_with_email_button"));
          // Act
          await tester.pumpAndSettle();
          await tester.tap(signIn);
          await tester.pump(const Duration(seconds: 1));
          // Assert
          verifyNever(mockAuth.resetPassword(email: anyNamed("named")));
      });
    }
    

    What I'm getting is an error on the anyNamed("named") as its only mocked with, not using @GenerateMocks([IAuthRepository]) like in my unit tests. What I don't understand, or can find the answer, is how am I supposed to use the @GenerateMocks with Injectable? Or is there another way to generate better mocks for my testing env?

    Any guidance or suggestions is much appreacted.