Class 'MockFirebaseAuth' has no instance method 'fetchSignInMethodsForEmail' with matching arguments

Viewed 170

I can't figure out why I get this error when trying to run this widget test. I just want to mock a user that is in the database so when I type in an email, I either go to my PasswordFormLogin widget if the user email matches the user email that is in the mock user or then go to my RegistrationLoginForm widget.

Debug Console:

══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞══════════════════════════════════════════════════
The following NoSuchMethodError was thrown running a test:
Class 'MockFirebaseAuth' has no instance method 'fetchSignInMethodsForEmail' with matching
arguments.
Receiver: Instance of 'MockFirebaseAuth'
Tried calling: fetchSignInMethodsForEmail("bob@thebuilder.com")
Found: fetchSignInMethodsForEmail(String) => Future<List<String>>

When the exception was thrown, this was the stack:
#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:54:5)
#1      MockFirebaseAuth.noSuchMethod (package:firebase_auth_mocks/src/firebase_auth_mocks_base.dart:70:56)
#2      MockFirebaseAuth.fetchSignInMethodsForEmail (package:firebase_auth/src/firebase_auth.dart:226:24)
#3      ApplicationState.verifyEmail (file:///C:/Users/calvo/Documents/flutter/projects/freegapp/test/LoginFlow_widget_test.dart:92:32)
#4      LoginFlow.build.<anonymous closure> (package:freegapp/LoginFlow.dart:57:45)
#5      _EmailFormState.build.<anonymous closure> (package:freegapp/src/LoginFlowStuff/EmailFormLogin.dart:73:40)
#6      _EmailFormState.build.<anonymous closure> (package:freegapp/src/LoginFlowStuff/EmailFormLogin.dart:64:32)
#7      _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:989:21)
#8      GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:182:24)
#9      TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:607:11)
#10     BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:296:5)
#11     BaseTapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:230:7)
#12     PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:475:9)
#13     PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:93:12)
#14     PointerRouter._dispatchEventToRoutes.<anonymous closure> (package:flutter/src/gestures/pointer_router.dart:138:9)
#15     _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:397:8)
#16     PointerRouter._dispatchEventToRoutes (package:flutter/src/gestures/pointer_router.dart:136:18)
#17     PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:122:7)
#18     GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:439:19)
#19     GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:419:22)
#20     RendererBinding.dispatchEvent (package:flutter/src/rendering/binding.dart:287:11)
#21     GestureBinding._handlePointerEventImmediately (package:flutter/src/gestures/binding.dart:374:7)
#22     GestureBinding.handlePointerEvent (package:flutter/src/gestures/binding.dart:338:5)
#23     TestWidgetsFlutterBinding.handlePointerEvent (package:flutter_test/src/binding.dart:507:13)
#24     WidgetTester.sendEventToBinding.<anonymous closure> (package:flutter_test/src/widget_tester.dart:792:15)
#25     WidgetTester.sendEventToBinding.<anonymous closure> (package:flutter_test/src/widget_tester.dart:791:39)
#28     TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:71:41)
#29     WidgetTester.sendEventToBinding (package:flutter_test/src/widget_tester.dart:791:27)
#30     TestGesture.up.<anonymous closure> (package:flutter_test/src/test_pointer.dart:392:24)
#31     TestGesture.up.<anonymous closure> (package:flutter_test/src/test_pointer.dart:390:39)
#34     TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:71:41)
#35     TestGesture.up (package:flutter_test/src/test_pointer.dart:390:27)
#36     WidgetController.tapAt.<anonymous closure> (package:flutter_test/src/controller.dart:278:21)
<asynchronous suspension>
<asynchronous suspension>
(elided 5 frames from dart:async and package:stack_trace)

The test description was:
  EmailFormLogin
══════════════════════════════════════════════════════════════════════════════════════════════════

LoginFlow_widget_test.dart:

import 'package:flutter_test/flutter_test.dart';
import 'package:firebase_auth_mocks/firebase_auth_mocks.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:provider/provider.dart';
import 'package:firebase_core/firebase_core.dart';

import 'package:freegapp/LoginFlow.dart';

final tUser = MockUser(
  isAnonymous: false,
  uid: 'T3STU1D',
  email: 'bob@thebuilder.com',
  displayName: 'Bob Builder',
  phoneNumber: '0800 I CAN FIX IT',
  photoURL: 'http://photos.url/bobbie.jpg',
  refreshToken: 'some_long_token',
);

void main() {
// TextField widgets require a Material widget ancestor.
// In material design, most widgets are conceptually "printed" on a sheet of material. In Flutter's
// material library, that material is represented by the Material widget. It is the Material widget
// that renders ink splashes, for instance. Because of this, many material library widgets require that
// there be a Material widget in the tree above them.
// To introduce a Material widget, you can either directly include one, or use a widget that contains
// Material itself, such as a Card, Dialog, Drawer, or Scaffold.
  testWidgets('EmailFormLogin', (WidgetTester tester) async {
    await tester.pumpWidget(ChangeNotifierProvider(
        create: (context) => ApplicationState(),
        builder: (context, _) => MaterialApp(
            title: 'Freegap',
            theme: ThemeData(
              primarySwatch: Colors.blue,
            ),
            home: Consumer<ApplicationState>(
                builder: (context, appState, _) => LoginFlow(
                    email: appState.email,
                    loginState: appState.loginState,
                    startLoginFlow: appState.startLoginFlow,
                    verifyEmail: appState.verifyEmail,
                    signInWithEmailAndPassword:
                        appState.signInWithEmailAndPassword,
                    cancelRegistration: appState.cancelRegistration,
                    registerAccount: appState.registerAccount,
                    signOut: appState.signOut,
                    key: Key('LoginFlow'))))));
    expect(find.byKey(Key('EmailFormLogin')), findsOneWidget);
    // Enter 'bob@thebuilder.com' into the TextField.
    await tester.enterText(find.byType(TextField), 'bob@thebuilder.com');
    await tester.tap(find.byType(ElevatedButton));
    expect(find.byKey(Key('EmailFormLogin')), findsOneWidget);
  });
}

class ApplicationState extends ChangeNotifier {
  ApplicationState() {
    init();
  }
  final auth = MockFirebaseAuth(mockUser: tUser);

  Future<void> init() async {
    await Firebase.initializeApp();

    auth.userChanges().listen((user) {
      if (user != null) {
        _loginState = ApplicationLoginState.loggedIn;
      } else {
        _loginState = ApplicationLoginState.loggedOut;
      }
      notifyListeners();
    });
  }

  ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
  ApplicationLoginState get loginState => _loginState;

  String? _email;
  String? get email => _email;

  void startLoginFlow() {
    _loginState = ApplicationLoginState.emailAddress;
    notifyListeners();
  }

  void verifyEmail(
    String email,
    void Function(FirebaseAuthException e) errorCallback,
  ) async {
    try {
      var methods = await auth.fetchSignInMethodsForEmail(email);
      if (methods.contains('password')) {
        _loginState = ApplicationLoginState.password;
      } else {
        _loginState = ApplicationLoginState.register;
      }
      _email = email;
      notifyListeners();
    } on FirebaseAuthException catch (e) {
      errorCallback(e);
    }
  }

  void signInWithEmailAndPassword(
    String email,
    String password,
    void Function(FirebaseAuthException e) errorCallback,
  ) async {
    try {
      await auth.signInWithEmailAndPassword(
        email: email,
        password: password,
      );
    } on FirebaseAuthException catch (e) {
      errorCallback(e);
    }
  }

  void cancelRegistration() {
    _loginState = ApplicationLoginState.loggedOut;
    notifyListeners();
  }

  void registerAccount(String email, String displayName, String password,
      void Function(FirebaseAuthException e) errorCallback) async {
    try {
      var credential = await auth.createUserWithEmailAndPassword(
          email: email, password: password);
      await credential.user!.updateDisplayName(displayName);
    } on FirebaseAuthException catch (e) {
      errorCallback(e);
    }
  }

  void signOut() {
    auth.signOut();
  }
}

LoginFlow.dart:

import 'package:flutter/material.dart';
import 'package:freegapp/src/LoginFlowStuff/EmailFormLogin.dart';
import 'package:freegapp/src/LoginFlowStuff/PasswordFormLogin.dart';
import 'package:freegapp/Sell.dart';
import 'package:freegapp/src/LoginFlowStuff/RegisterFormLogin.dart';
import 'package:freegapp/src/style_widgets.dart';

enum ApplicationLoginState {
  loggedOut,
  emailAddress,
  register,
  password,
  loggedIn,
}

class LoginFlow extends StatelessWidget {
  const LoginFlow({
    required this.loginState,
    required this.email,
    required this.startLoginFlow,
    required this.verifyEmail,
    required this.signInWithEmailAndPassword,
    required this.cancelRegistration,
    required this.registerAccount,
    required this.signOut,
    Key? key,
  }) : super(key: key);

  final ApplicationLoginState loginState;
  final String? email;
  final void Function() startLoginFlow;
  //  typedef myFunction = final void Function(String email, void Function(Exception e) error,);
  final void Function(
    String email,
    void Function(Exception e) error,
  ) verifyEmail; //  myFunction verifyEmail() = {}
  final void Function(
    String email,
    String password,
    void Function(Exception e) error,
  ) signInWithEmailAndPassword;
  final void Function() cancelRegistration;
  final void Function(
    String email,
    String displayName,
    String password,
    void Function(Exception e) error,
  ) registerAccount;
  final void Function() signOut;

  @override
  Widget build(BuildContext context) {
    switch (loginState) {
      case ApplicationLoginState.loggedOut:
        return EmailFormLogin(
            key: Key('EmailFormLogin'),
            callback: (email) => verifyEmail(
                email, (e) => _showErrorDialog(context, 'Invalid email', e)));
      case ApplicationLoginState.password:
        return PasswordFormLogin(
          key: Key('PasswordFormLogin'),
          email: email!,
          login: (email, password) {
            signInWithEmailAndPassword(email, password,
                (e) => _showErrorDialog(context, 'Failed to sign in', e));
          },
        );
      case ApplicationLoginState.register:
        return RegisterFormLogin(
          key: Key('RegisterFormLogin'),
          email: email!,
          cancel: () {
            cancelRegistration();
          },
          registerAccount: (
            email,
            displayName,
            password,
          ) {
            registerAccount(
                email,
                displayName,
                password,
                (e) =>
                    _showErrorDialog(context, 'Failed to create account', e));
          },
        );
      case ApplicationLoginState.loggedIn:
        return Sell(
          logout: () {
            signOut();
          },
          key: Key('Sell'),
        );
      default:
        return Row(
          children: const [
            Text("Internal error, this shouldn't happen..."),
          ],
        );
    }
  }
}

void _showErrorDialog(BuildContext context, String title, Exception e) {
  showDialog<void>(
    context: context,
    builder: (context) {
      return AlertDialog(
        title: Text(
          title,
          style: const TextStyle(fontSize: 24),
        ),
        content: SingleChildScrollView(
          child: ListBody(
            children: <Widget>[
              Text(
                '${(e as dynamic).message}',
                style: const TextStyle(fontSize: 18),
              ),
            ],
          ),
        ),
        actions: <Widget>[
          StyledButton(
            onPressed: () {
              Navigator.of(context).pop();
            },
            child: const Text(
              'OK',
              style: TextStyle(color: Colors.deepPurple),
            ),
          ),
        ],
      );
    },
  );
}

EmailFormLogin.dart:

import 'package:flutter/material.dart';
import 'package:freegapp/src/style_widgets.dart';

class EmailFormLogin extends StatefulWidget {
  const EmailFormLogin({
    required this.callback,
    Key? key,
  }) : super(key: key);
  final void Function(String email) callback;

  @override
  _EmailFormState createState() => _EmailFormState();
}

class _EmailFormState extends State<EmailFormLogin> {
  // Create a global key that uniquely identifies the Form widget
  // and allows validation of the form.
  //
  // Note: This is a `GlobalKey<FormState>`,
  // not a GlobalKey<_EmailFormState>.
  final _formKey = GlobalKey<FormState>(debugLabel: '_EmailFormState');
  final _controller = TextEditingController();

  @override
  void dispose() {
    _controller.dispose();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Column(children: <Widget>[
      Form(
        key: _formKey,
        child: Column(
          children: <Widget>[
            const Header('Sign in / Register'),
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 24),
              child: TextFormField(
                controller: _controller,
                keyboardType: TextInputType.emailAddress,
                decoration: const InputDecoration(
                  hintText: 'Enter your email',
                  prefixIcon: Icon(Icons.mail),
                ),
                validator: (value) {
                  if (value!.isEmpty) {
                    return 'Enter your email address';
                  }
                  return null;
                },
              ),
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.end,
              children: <Widget>[
                Padding(
                  padding: const EdgeInsets.symmetric(
                      vertical: 16.0, horizontal: 30),
                  child: ElevatedButton(
                    onPressed: () async {
                      // The FormState class contains the validate() method.
                      // When the validate() method is called, it runs the validator() function
                      // for each text field in the form. If everything looks good,
                      // the validate() method returns true. If any text field contains errors,
                      // the validate() method rebuilds the form to display any error messages and returns false.
                      // add ! to assert that it isn’t null (and to throw an exception if it is).
                      if (_formKey.currentState!.validate()) {
                        // If the form is valid,
                        widget.callback(_controller
                            .text); // call to parent with current string the user is editing
                      }
                    },
                    child: const Text('NEXT'),
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    ]));
  }
}
1 Answers

The firebase_auth_mocks package currently does not support mocking the fetchSignInMethodsForEmail() method.

A suggestion to get past the error is to replace this line:

      var methods = await auth.fetchSignInMethodsForEmail(email);

with something like this:

      var methods = await Future.value(['password']);
Related