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'),
),
),
],
),
],
),
),
]));
}
}