类“MockFirebaseAuth”没有带有匹配参数的实例方法“createUserWithEmailAndPassword”

时间:2021-07-16 20:49:58

标签: firebase flutter firebase-authentication flutter-test

我不知道如何通过这个错误。我输入了一封与 MockUser 无关的电子邮件,然后它会转到 RegisterFormLogin 小部件。当我在字段中输入内容然后单击保存并登录的 ElevatedButton 时,我收到此错误:

调试控制台

══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following NoSuchMethodError was thrown running a test:
Class 'MockFirebaseAuth' has no instance method 'createUserWithEmailAndPassword' with matching
arguments.
Receiver: Instance of 'MockFirebaseAuth'
Tried calling: createUserWithEmailAndPassword(email: "dom@thebuilder.com", password: "T3STU1D")
Found: createUserWithEmailAndPassword({required String email, required String password}) =>
Future<UserCredential>

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.createUserWithEmailAndPassword (package:firebase_auth/src/firebase_auth.dart:205:26)
#3      ApplicationState.registerAccount (file:///C:/Users/calvo/Documents/flutter/projects/freegapp/test/LoginFlow_widget_test.dart:153:35)
#4      LoginFlow.build.<anonymous closure> (package:freegapp/LoginFlow.dart:80:28)
#5      _RegisterFormState.build.<anonymous closure> (package:freegapp/src/LoginFlowStuff/RegisterFormLogin.dart:105:51)
#6      _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:989:21)
#7      GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:182:24)
#8      TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:607:11)
#9      BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:296:5)
#10     BaseTapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:230:7)
#11     PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:475:9)
#12     PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:93:12)
#13     PointerRouter._dispatchEventToRoutes.<anonymous closure> (package:flutter/src/gestures/pointer_router.dart:138:9)
#14     _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:397:8)
#15     PointerRouter._dispatchEventToRoutes (package:flutter/src/gestures/pointer_router.dart:136:18)
#16     PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:122:7)
#17     GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:439:19)
#18     GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:419:22)
#19     RendererBinding.dispatchEvent (package:flutter/src/rendering/binding.dart:287:11)
#20     GestureBinding._handlePointerEventImmediately (package:flutter/src/gestures/binding.dart:374:7)
#21     GestureBinding.handlePointerEvent (package:flutter/src/gestures/binding.dart:338:5)
#22     TestWidgetsFlutterBinding.handlePointerEvent (package:flutter_test/src/binding.dart:507:13)
#23     WidgetTester.sendEventToBinding.<anonymous closure> (package:flutter_test/src/widget_tester.dart:792:15)
#24     WidgetTester.sendEventToBinding.<anonymous closure> (package:flutter_test/src/widget_tester.dart:791:39)
#27     TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:71:41)
#28     WidgetTester.sendEventToBinding (package:flutter_test/src/widget_tester.dart:791:27)
#29     TestGesture.up.<anonymous closure> (package:flutter_test/src/test_pointer.dart:392:24)
#30     TestGesture.up.<anonymous closure> (package:flutter_test/src/test_pointer.dart:390:39)
#33     TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:71:41)
#34     TestGesture.up (package:flutter_test/src/test_pointer.dart:390:27)
#35     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:
  Go to register page
════════════════════════════════════════════════════════════════════════════════════════════════════

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_platform_interface/firebase_auth_platform_interface.dart';
import 'package:provider/provider.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',
);

final auth = MockFirebaseAuth(mockUser: tUser);
final theError = FirebaseAuthException(
  code: 'wrong-password:',
  message: 'The password entered is incorrect',
);

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('Login with accepted email and password',
      (WidgetTester tester) async {
    await tester.pumpWidget(ChangeNotifierProvider(
        create: (context) => ApplicationState(),
        builder: (context, _) => MaterialApp(
            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(TextFormField), 'bob@thebuilder.com');
    await tester.tap(find.byType(ElevatedButton));
    await tester.pump();
    expect(find.byKey(Key('PasswordFormLogin')), findsOneWidget);
    await tester.enterText(
        find.byKey(Key('PasswordFormLoginTextFormField')), 'T3STU1D');
    await tester.tap(find.text('SIGN IN'));
    await tester.pump();
    expect(find.byKey(Key('Sell')), findsOneWidget);
    // expect(find.byKey(Key('AlertDialogLoginFlow')), findsOneWidget);
  });
  testWidgets('Go to register page', (WidgetTester tester) async {
    await tester.pumpWidget(ChangeNotifierProvider(
        create: (context) => ApplicationState(),
        builder: (context, _) => MaterialApp(
            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 'dom@thebuilder.com' into the TextField.
    await tester.enterText(find.byType(TextFormField), 'dom@thebuilder.com');
    await tester.tap(find.byType(ElevatedButton));
    await tester.pump();
    expect(find.byKey(Key('RegisterFormLogin')), findsOneWidget);
    await tester.enterText(find.byKey(Key('DisplayNameNameRegisterFormLogin')),
        'dom@thebuilder.com');
    await tester.enterText(
        find.byKey(Key('PasswordRegisterFormLogin')), 'T3STU1D');
    await tester.tap(find.byType(ElevatedButton));
    await tester.pump();
  });
}

class ApplicationState extends ChangeNotifier {
  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 Future.value(['password']);
      if (methods.contains('password') && email == 'bob@thebuilder.com') {
        _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 {
      final result = await auth.signInWithEmailAndPassword(
        email: email,
        password: password,
      );
      if (result.user == tUser) {
        _loginState = ApplicationLoginState.loggedIn;
      } else {
        errorCallback(theError);
      }
      notifyListeners();
    } 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);
      _loginState = ApplicationLoginState.loggedIn;
      notifyListeners();
    } 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(
        key: Key('AlertDialogLoginFlow'),
        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),
            ),
          ),
        ],
      );
    },
  );
}

RegisterFormLogin.dart

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

class RegisterFormLogin extends StatefulWidget {
  const RegisterFormLogin({
    required this.registerAccount,
    required this.cancel,
    required this.email,
    Key? key,
  }) : super(key: key);
  final String email;
  final void Function(String email, String displayName, String password)
      registerAccount;
  final void Function() cancel;
  @override
  _RegisterFormState createState() => _RegisterFormState();
}

class _RegisterFormState extends State<RegisterFormLogin> {
  final _formKey = GlobalKey<FormState>(debugLabel: '_RegisterFormState');
  final _emailController = TextEditingController();
  final _displayNameController = TextEditingController();
  final _passwordController = TextEditingController();

  @override
  void initState() {
    super.initState();
    _emailController.text = widget.email;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Column(
      children: [
        const Header('Create Account'),
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Form(
            key: _formKey,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 24),
                  child: TextFormField(
                    controller: _emailController,
                    decoration: const InputDecoration(
                      hintText: 'Enter your email',
                    ),
                    validator: (value) {
                      if (value!.isEmpty) {
                        return 'Enter your email address to continue';
                      }
                      return null;
                    },
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 24),
                  child: TextFormField(
                    key: Key('DisplayNameNameRegisterFormLogin'),
                    controller: _displayNameController,
                    decoration: const InputDecoration(
                      hintText: 'First & last name',
                    ),
                    validator: (value) {
                      if (value!.isEmpty) {
                        return 'Enter your account name';
                      }
                      return null;
                    },
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 24),
                  child: TextFormField(
                    key: Key('PasswordRegisterFormLogin'),
                    controller: _passwordController,
                    decoration: const InputDecoration(
                      hintText: 'Password',
                    ),
                    obscureText: true,
                    validator: (value) {
                      if (value!.isEmpty) {
                        return 'Enter your password';
                      }
                      return null;
                    },
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.symmetric(vertical: 16),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.end,
                    children: [
                      TextButton(
                        onPressed: widget.cancel,
                        child: const Text('CANCEL'),
                      ),
                      const SizedBox(width: 16),
                      ElevatedButton(
                        onPressed: () {
                          if (_formKey.currentState!.validate()) {
                            widget.registerAccount(
                              _emailController.text,
                              _displayNameController.text,
                              _passwordController.text,
                            );
                          }
                        },
                        child: const Text('SAVE'),
                      ),
                      const SizedBox(width: 30),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
      ],
    ));
  }
}

1 个答案:

答案 0 :(得分:1)

https://pub.dev/packages/firebase_auth_mocks 目前不支持 FirebaseAuth.createUserWithEmailAndPassword 方法。

您可以通过像这样定义自己的 createUserWithEmailAndPassword 方法来解决这个问题:

Future<MockUserCredential> createUserWithEmailAndPassword({@required String email, @required String password}) async {
  return MockUserCredential(false, tUser);
}

MockUserCredential 可以通过添加以下行来导入:

// ignore: implementation_imports
import 'package:firebase_auth_mocks/src/mock_user_credential.dart';

使用实现导入的原因是类没有随库一起导出。

并且您可以通过替换这一行来使用新创建的 createUserWithEmailAndPassword 方法:

 var credential = await auth.createUserWithEmailAndPassword(
          email: email, password: password);

这样:

var credential = await createUserWithEmailAndPassword(
          email: email, password: password);