我不知道如何传递这个错误。我键入一封与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),
],
),
),
],
),
),
),
],
));
}
}
包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);