0 purchases
authenticator
Flutter package: authenticator #
A firebase authentication package that includes mocked platform
channels so login flows can be tested with flutter test.
Short and sweet (TLDR;) #
var auth = Authenticator();
var userId = await auth.signInWithGoogle();
print(userId);
auth.signOut();
copied to clipboard
Full documentation #
Documentation generated by dartdoc can be found here.
Getting started #
First, the if the app will be using Authenticator, it also needs to
have plugins for:
Facebook Login
Firebase Authentication
Google Sign In
Follow the instructions for each of those plugins so the platform the
app is targeting can authenticate.
Setting those up will likely be the most time-consuming part
of getting Authentication working, as the app will need to be
registered with Firebase and Facebook.
Add authenticator to your pubspec.yaml file as a dependency. For
example, to use authenticator from the master branch in this repo,
add authenticator to the dependencies section, like so:
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
firebase_auth:
google_sign_in:
flutter_facebook_login: ^3.0.0
authenticator: ^0.1.2
copied to clipboard
Using Authenticator #
The example folder contains a bare-bones app that includes Google and
Facebook sign in code. The example will not run on a device platform,
since it has no credentials from Google or Facebook, which are
required to build (signing keys, google-services.json, Facebook API
key, etc...). The example DOES run under the flutter test framework, so
to see it in action, attach a debugger and run the test.
As a Provider #
Add a single parameter to your application widget, taking an
Authenticator as an argument. This is to allow both device platforms
and unit tests to supply an appropriate Authenticator.
class MyApp extends StatelessWidget {
// This widget is the root of your application
final Authenticator auth;
MyApp(Authenticator authenticator) : auth = authenticator;
@override
Widget build(BuildContext context) {
return ListenableProvider<Authenticator>.value(
value: auth,
child: MaterialApp(
title: 'Authenticator Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Root(),
));
}
}
copied to clipboard
Notice the ListenableProvider<Authenticator> in the build override.
When an authentication event changes the authentication state of the
user and application, the widget tree will be updated.
In the application's main(), pass a default Authenticator to the
application widget.
void main() {
var auth = Authenticator();
runApp(MyApp(auth));
}
copied to clipboard
In the application widget tests, a different constructor can used to
construct an Authenticator that mocks platform channels and allows
tests to be run.
void main() {
testWidgets('Can sign in and sign out', (WidgetTester tester) async {
var auth = Authenticator.createMocked();
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp(auth));
/// ...
copied to clipboard
Most applications will decide whether to direct a user to a signin
page or to the application home page based on their authentication
status.
class Root extends StatelessWidget {
Root() : super(key: ValueKey('Root Page'));
@override
Widget build(BuildContext context) {
final auth = Provider.of<Authenticator>(context);
return ListenableProvider<Authenticator>.value(
value: auth,
child: Consumer(builder: (context, Authenticator auth, _) {
switch (auth.authState) {
case AuthState.AUTHENTICATED:
return Home();
break;
default:
return Login();
break;
}
}));
}
}
copied to clipboard
And of course, the login page will have buttons asking the user to
choose sign in.
class Login extends StatelessWidget {
Login() : super(key: ValueKey('Login Page'));
@override
Widget build(BuildContext context) {
final auth = Provider.of<Authenticator>(context);
return ListenableProvider<Authenticator>.value(
value: auth,
child: Consumer(builder: (context, Authenticator auth, _) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Text("Sign in with ..."),
FlatButton(
key: ValueKey('signInWithGoogle button'),
child: Text('Google'),
onPressed: () {
auth.signInWithGoogle();
}),
FlatButton(
key: ValueKey('signInWithFacebook button'),
child: Text("Facebook"),
onPressed: () {
auth.signInWithFacebook();
})
]);
}));
}
}
copied to clipboard
They may also have a sign in with email option that presents a page
with text fields for an email address and password. The onPressed
value for that page would simply call:
onPressed: () { auth.signInWithEmailAndPassword(email, password); }
copied to clipboard
The application could also provide new user registration with Firebase
using an email address and password, or to request an password reset
email to be sent.
See:
Future<String> createUserWithEmailAndPassword(String email, String password) async
Future<void> sendPasswordResetEmail(String emailAddress) async
Lastly, there should be an option to sign out as well from the home
page.
class Home extends StatelessWidget {
Home() : super(key: ValueKey('Home Page'));
@override
Widget build(BuildContext context) {
final auth = Provider.of<Authenticator>(context);
return ListenableProvider<Authenticator>.value(
value: auth,
child: Consumer(builder: (context, Authenticator auth, _) {
return FlatButton(
key: ValueKey('signOut button'),
child: Text("Sign out"),
onPressed: () {
auth.signOut();
});
}));
}
}
copied to clipboard
Other goodies #
Authenticator exposes a Firebase user, which in turn provides a lot of
useful data, including things like a photoUrl to display, email
address, and a UID that is useful in conjunction with Firebase storage
to uniquily identify users in documents or paths.
If your IDE supports showing documentation for exported member data and
methods (as Visual Studio Code does,for example), explore a bit, hover
over methods or members that seem interesting. They include additional
information and examples.
Advanced testing #
The behavior of each MockXxxChannel can be modified by setting static
member values for the duration of whichever tests need to execute.
Overriding responses #
The responses sent from the Mocked channels included with Authenticator
can be customized to trigger specific failures to increase and improve
code coverage.
The test suite included with Authenticator has examples showing how to
accomplish this.
test('Facebook authentication fail no resut', () async {
MockFacebookLoginChannel.loginResponse['status'] = 'error';
var uid = await auth.signInWithFacebook();
expect(uid, isNull,
reason:
'signInWithFacebook should not return a result when there is an error');
expect(auth.authState, AuthState.UNAUTHENTICATED,
reason: 'User is not in an UNAUTHENTICATED state after signing out!');
await auth.signOut();
MockFacebookLoginChannel.resetLoginResponse();
});
copied to clipboard
Note: Be sure to call resetLoginResponse() unless it should remain set
for the next call to the mocked platform channel.
Forcing exceptions #
The mocked channels can be forced to throw on any operation by setting
throwOnEveryChannelMessage to the desired exception on the channel.
Just be sure to set it to null when the tests should no longer trigger
exceptions.
For example:
test('Always Throw Up for Google', () async {
MockGoogleSignInChannel.throwOnEveryChannelMessage = PlatformException(code: 'ALWAYS_THROW_UP');
var exceptionCaught = false;
try {
await auth.signInWithGoogle();
} on PlatformException catch (e) {
MockGoogleSignInChannel.throwOnEveryChannelMessage = null;
expect(e.code, 'ALWAYS_THROW_UP',
reason:
'Expected the Platform exception code to send ALWAYS_THROW_UP');
exceptionCaught = true;
} finally {
MockGoogleSignInChannel.throwOnEveryChannelMessage = null;
}
expect(exceptionCaught, true,
reason: 'An exception should have been thrown, but was not');
MockGoogleSignInChannel.throwOnEveryChannelMessage = null;
});
copied to clipboard
This could easily be adapted to a widget test suite that will take a
user back to the application splash or login screen if an unexpected
exception is thrown from a plugin. Just set the
MockXxxChannel.throwOnEveryChannelMessage to null once the app should
no longer throw for testing purposes.
Known issues #
The mocked platform channels do not handle every possible method call
that may be sent by some providers. This is either because the
providers have no publicly exposed interface to trigger the calls, or
more likely, are not functionality that anyone is interested in using
yet.
If your application does need to support these, please do open an issue
with as much information as possible describing the scenario so the
support can be added. Better yet, submit a pull request!
To track down unhandled messages within a test framework, simply set
throwOnUnhandledChannelMessages on whichever mock channel is of
interest. For example:
MockFirebaseAuthChannel.throwOnUnhandledChannelMessages = true;
copied to clipboard
If an authentication provider invokes a method on a platform channel
that is unhandled, it will throw in the test. If you have such a test
handy and would like to have support for that message in Authenticator,
send the test along when opening a new issue.
For personal and professional use. You cannot resell or redistribute these repositories in their original state.
There are no reviews.