A month of Flutter: mocking Firebase Auth in tests
Tags: flutter, testing, tutorial, firebase
Published: 2018-12-20 04:08:43

_Originally published on [bendyworks.com](https://bendyworks.com/blog/a-month-of-flutter-mocking-firebase-auth-in-tests)._

[Sign in with Google](https://bendyworks.com/blog/a-month-of-flutter-sign-in-with-google) adds a wildcard to the app: external services. External services are usually avoided in test environments because they create variance and complexity so today I'm going to talk about creating [mocks](https://pub.dartlang.org/packages/mockito) to simulate interacting with these third-party APIs.

Mocks in tests provide two main features: stubbing methods and objects so they behave like real things and validation that methods and properties are called as expected.

To create a mock for `FirebaseAuth` is as simple as:

~~~dart
class FirebaseAuthMock extends Mock implements FirebaseAuth {}
~~~

I've also created `FirebaseUserMock`, `GoogleSignInMock`, `GoogleSignInAuthenticationMock`, and `GoogleSignInAccountMock`. For now they are mostly simple mocks but they can be created with more complex states and responses stubbed.

The majority of the tests written for yesterday was testing the `Auth` service.

~~~dart
group('Auth', () {
  final FirebaseAuthMock firebaseAuthMock = FirebaseAuthMock();
  final GoogleSignInMock googleSignInMock = GoogleSignInMock();
  final FirebaseUserMock firebaseUserMock = FirebaseUserMock();
  final GoogleSignInAccountMock googleSignInAccountMock =
      GoogleSignInAccountMock();
  final GoogleSignInAuthenticationMock googleSignInAuthenticationMock =
      GoogleSignInAuthenticationMock();
  final Auth auth = Auth(
    firebaseAuth: firebaseAuthMock,
    googleSignIn: googleSignInMock,
  );

  test('signInWithGoogle returns a user', () async {
    when(googleSignInMock.signIn()).thenAnswer((_) =>
        Future<GoogleSignInAccountMock>.value(googleSignInAccountMock));

    when(googleSignInAccountMock.authentication).thenAnswer((_) =>
        Future<GoogleSignInAuthenticationMock>.value(
            googleSignInAuthenticationMock));

    when(firebaseAuthMock.signInWithGoogle(
      idToken: googleSignInAuthenticationMock.idToken,
      accessToken: googleSignInAuthenticationMock.accessToken,
    )).thenAnswer((_) => Future<FirebaseUserMock>.value(firebaseUserMock));

    expect(await auth.signInWithGoogle(), firebaseUserMock);

    verify(googleSignInMock.signIn()).called(1);
    verify(googleSignInAccountMock.authentication).called(1);
    verify(firebaseAuthMock.signInWithGoogle(
      idToken: googleSignInAuthenticationMock.idToken,
      accessToken: googleSignInAuthenticationMock.accessToken,
    )).called(1);
  });
});
~~~

First I'm instantiating five mock objects and an instance of `Auth` which is getting passed the `FirebaseAuth` and `GoogleSignIn` mocks. These are the primary mocks that represent those two services in the functional code.

Within the `test` example I'm specifying that when `signIn` gets called on the `GoogleSignIn` mock, it should return an instance of the `GoogleSignInAccount` mock using `thenAnswer`. [`thenAnswer`](https://pub.dartlang.org/documentation/mockito/latest/mockito/PostExpectation/thenAnswer.html) is just like [`thenReturn`](https://pub.dartlang.org/documentation/mockito/latest/mockito/PostExpectation/thenReturn.html) but is for returning asynchronous values.

There are then stubs for two more external services before the actual `signInWithGoogle` method being tested. This `expect` asserts the response is the mocked `FirebaseUser` that it should be.

After that I have three `verify` assertions. These check with the mocks to make sure the API methods were called with the expected inputs. With this test in place I can be reasonably sure that as long as the external services don't change their APIs, my code will keep working as desired.

The `Auth` tests need to be fleshed out some to make sure they handle error cases in the external services. There was an [issue](https://github.com/abraham/birb/issues/48) created to track adding this error handling.

The last part I want to test is that when a user taps on the Sign in with Google FAB, the `Auth` service will be called as expected. The pattern of mocks here is the same as above but just simpler.

~~~dart
testWidgets('Renders sign in', (WidgetTester tester) async {
  final AuthMock mock = AuthMock();
  // Build our app and trigger a frame.
  await tester.pumpWidget(MaterialApp(
    home: SignInFab(auth: mock),
  ));

  when(mock.signInWithGoogle())
      .thenAnswer((_) => Future<FirebaseUserMock>.value(FirebaseUserMock()));

  expect(find.byType(Image), findsOneWidget);
  expect(find.byType(FloatingActionButton), findsOneWidget);
  expect(find.text('Sign in with Google'), findsOneWidget);

  await tester.tap(find.byType(FloatingActionButton));

  verify(mock.signInWithGoogle()).called(1);
});
~~~

[Testing](https://flutter.io/docs/testing) is a critical aspect of making sure applications continue to work as they are changed. These are unit tests and widget tests which are important but there is a third type of test: integration. Integration tests are really good for making sure a full feature flow continues to work. I'll be working on adding integration tests in the future.

## Code changes

- [#44 Add sign in service](https://github.com/abraham/birb/pull/44)