Injecting generated mock classes into test" environment
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.
Lloyd Richards
Updated on January 01, 2023Comments
-
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 usewhen
andverify
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.