New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mocking AWS Amplify Session in methods #139
Comments
Hi @garrettlove8 👋 test("Set isAuthenticated to true", () {
MockAuth auth = MockAuth();
when(auth.fetchAuthSession).thenAnswer((_) async {
return Future(() => auth); // use auth
});
final user = UserRepository(auth: auth);
expect(user.isAuthenticated, false);
user.setAuthenticatedStatus(true);
expect(user.isAuthenticated, true);
}); |
@felangel Thanks for such a quick response. After trying that, it gives the error:
|
Np! That means you probably need to return a mock instance of AuthSession rather than Auth. If you’re still having trouble the easiest thing would be if you could provide a link to a minimal reproduction sample, thanks! |
Not sure that I can fully provide a reproducible sample since it realize on an AWS Session, but at the very least here's the full code:
|
@felangel Hey now that I think about it, I believe I was using a mocked instance of
Only problem is it looks like this may have been causing the error. |
Yeah that’s what I was trying to point out. You shouldn’t be returning a different MockAuthSession instance because that instance doesn’t have any stubs. |
@felangel Ok I think I'm a little confused. Is the issue that even though I'm using a mocked Auth object and stubbing If so, I've tried this code, but it still gives the original error:
|
@garrettlove8 I use import 'dart:async';
import 'package:amplify_auth_cognito/amplify_auth_cognito.dart';
import 'package:amplify_auth_plugin_interface/amplify_auth_plugin_interface.dart';
import 'package:amplify_flutter/amplify.dart';
import 'package:auth_repository/auth_repository.dart';
import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';
class MockAuthPlugin extends Mock implements AuthPluginInterface {}
class MockCognitoAuthSession extends Mock implements CognitoAuthSession {}
class MockAWSCognitoUserPoolTokens extends Mock
implements AWSCognitoUserPoolTokens {}
class FakeSessionRequest extends Fake implements AuthSessionRequest {}
class FakeSignInRequest extends Fake implements SignInRequest {}
class FakeSignOutRequest extends Fake implements SignOutRequest {}
void main() {
group('AuthRepository', () {
late AuthRepository authRepository;
final auth = MockAuthPlugin();
final authSession = MockCognitoAuthSession();
final tokens = MockAWSCognitoUserPoolTokens();
setUpAll(() {
registerFallbackValue(FakeSessionRequest());
registerFallbackValue(FakeSignInRequest());
when(() => auth.streamController)
.thenAnswer((_) => StreamController<dynamic>());
when(auth.addPlugin).thenAnswer((_) async {});
Amplify.addPlugin(auth);
});
setUp(() {
authRepository = AuthRepository();
});
test('defaults', () {
expect(authRepository.authStatus, isNotNull);
expect(authRepository.isAuthenticated, false);
});
group('initialize', () {
test('signedIn', () async {
when(() => auth.fetchAuthSession(request: any(named: 'request')))
.thenAnswer((_) async => AuthSession(isSignedIn: true));
await authRepository.initialize();
expect(authRepository.isAuthenticated, true);
});
test('!signedIn', () async {
when(() => auth.fetchAuthSession(request: any(named: 'request')))
.thenAnswer((_) async => AuthSession(isSignedIn: false));
await authRepository.initialize();
expect(authRepository.isAuthenticated, false);
});
});
group('signIn', () {
const email = 'mike@example.com';
const password = 'P@55w0r)!';
test('isSignedIn == true', () async {
when(() => auth.signIn(request: any(named: 'request')))
.thenAnswer((_) async => SignInResult(isSignedIn: true));
await authRepository.signIn(email: email, password: password);
expect(authRepository.isAuthenticated, true);
});
test('isSignedIn == false', () async {
when(() => auth.signIn(request: any(named: 'request')))
.thenAnswer((_) async => SignInResult(isSignedIn: false));
await authRepository.signIn(email: email, password: password);
expect(authRepository.isAuthenticated, false);
});
test('unsupported signIn', () async {
when(() => auth.signIn(request: any(named: 'request'))).thenAnswer(
(_) async => SignInResult(
isSignedIn: false,
nextStep: AuthNextSignInStep(signInStep: 'MFA')));
expect(
() async =>
await authRepository.signIn(email: email, password: password),
throwsA(isA<UnsupportedSignIn>()));
expect(authRepository.isAuthenticated, false);
});
test('cognito exception', () async {
when(() => auth.signIn(request: any(named: 'request')))
.thenThrow(PasswordResetRequiredException('mock error'));
expect(
() async =>
await authRepository.signIn(email: email, password: password),
throwsA(isA<SignInFailure>()));
expect(authRepository.isAuthenticated, false);
});
});
group('signOut', () {
setUp(() async {
when(() => auth.fetchAuthSession(request: any(named: 'request')))
.thenAnswer((_) async => AuthSession(isSignedIn: true));
await authRepository.initialize();
});
test('success', () async {
when(() => auth.signOut(request: any(named: 'request')))
.thenAnswer((_) async => SignOutResult());
await authRepository.signOut();
expect(authRepository.isAuthenticated, false);
});
test('error', () async {
when(() => auth.signOut(request: any(named: 'request')))
.thenThrow(PasswordResetRequiredException('mock error'));
expect(() async => await authRepository.signOut(),
throwsA(isA<SignOutFailure>()));
expect(authRepository.isAuthenticated, false);
});
});
});
} |
@mousedownmike Thank you so much for posting that, very helpful to get a bigger picture view and it appears as though I wasn't mocking out the correct things. I now have a stripped down version of your example but it seems to be working the same way. However, it now gets hung up on line 257 in invoke.dart - Here is what I have:
|
@garrettlove8, I'll see if I can make a minimal repo this evening to show the AuthRepository implementation. |
Thank you so much, that would amazing! |
I can't seem to get a minimal repo working with my current XCode setup but here's my import 'dart:async';
import 'package:amplify_auth_cognito/amplify_auth_cognito.dart';
import 'package:amplify_flutter/amplify.dart';
import 'package:auth_repository/auth_repository.dart';
import 'package:jwt_decoder/jwt_decoder.dart';
enum AuthStatus { unknown, authenticated, unauthenticated }
class AuthRepository {
final _authStatusStream = StreamController<AuthStatus>();
AuthStatus _currentStatus = AuthStatus.unauthenticated;
/// The Stream of [AuthStatus] changes.
Stream<AuthStatus> get authStatus => _authStatusStream.stream;
/// Returns the current authentication state as a bool.
bool get isAuthenticated => _currentStatus == AuthStatus.authenticated;
/// Initialize the repository with the current state of the
/// Amplify user session.
Future<void> initialize() async {
try {
AuthSession auth = await Amplify.Auth.fetchAuthSession();
(auth.isSignedIn) ? _authenticated() : _unauthenticated();
} catch (_) {
_unauthenticated();
}
}
/// Signs In the device using the supplied [email] and [password].
/// If successful the [AuthStatus] is sent on the [authStatus] Stream.
Future<void> signIn({required String email, required String password}) async {
SignInResult result;
try {
result = await Amplify.Auth.signIn(username: email, password: password);
} catch (_) {
_unauthenticated();
throw SignInFailure();
}
if (!result.isSignedIn) {
_unauthenticated();
if (result.nextStep != null) {
throw UnsupportedSignIn();
}
} else {
_authenticated();
}
}
/// Sign Out the currently authenticated user from the device.
Future<void> signOut() async {
try {
await Amplify.Auth.signOut();
} catch (_) {
throw SignOutFailure();
} finally{
_unauthenticated();
}
}
/// Set the current AuthStatus to authenticated and
/// add it to the Status Stream.
void _authenticated() {
_currentStatus = AuthStatus.authenticated;
_authStatusStream.add(AuthStatus.authenticated);
}
/// Set the current AuthStatus to unauthenticated and
/// add it to the Status Stream.
void _unauthenticated() {
_currentStatus = AuthStatus.unauthenticated;
_authStatusStream.add(AuthStatus.unauthenticated);
}
/// Retrieve a [Profile] for the currently authenticated
/// session.
Future<Profile> get profile async {
try {
final cognitoSession = await Amplify.Auth.fetchAuthSession(
options: CognitoSessionOptions(getAWSCredentials: true))
as CognitoAuthSession;
return Profile.fromJwt(
JwtDecoder.decode(cognitoSession.userPoolTokens!.idToken));
} catch (_) {
throw ProfileFailure();
}
}
/// Get the Authorization token String from the currently
/// signed in user.
Future<String> get apiToken async {
try {
final cognitoSession = await Amplify.Auth.fetchAuthSession(
options: CognitoSessionOptions(getAWSCredentials: true))
as CognitoAuthSession;
return cognitoSession.userPoolTokens!.idToken;
} catch (_) {
return '';
}
}
}
class SignInFailure implements Exception {}
class UnsupportedSignIn implements Exception {}
class SignOutFailure implements Exception {}
class ProfileFailure implements Exception {} |
I spoke too soon! Here's a minimal(ish) repo that's working for me. I had to hold back the flow_builder library version to get it to work so I'm probably due for some upgrades. https://github.com/mousedownco/amplify_auth You'll need to add your Cognito parameters here: |
Ok I think I'm getting it now. Looks like you're mocking your entire AuthRepository and skipping the amplify part all together. I update the way my code is structure to accommodate that:
Then in my test it becomes easier to mock things out: class MockUserRepository extends Mock implements UserRepository {}
I'm still a little fuzzy on how the AWSCredentials part works and if I take out the stubs for @felangel @mousedownmike This was insanely helpful, thank you so much! I spend most of my dev time in Golang and am just getting into Flutter/Dart but if there's anything I help out with here let me know! |
@garrettlove8 It depends on where you're looking. I test my You can see the Amplify initialize, sign in, and sign out operations tested in Then the FWIW, the backend for my app is written in Go and I only came to Flutter at the beginning of the year. The community @felangel has built around these libraries has been very helpful getting my existing skills applied to these new tools. P.S. I'm also realizing that naming my sample repo |
@mousedownmike Yeah for sure, that all makes sense. Thanks again for help guys. Closing the issue now. |
Originally posted this on SO here.
I have a UserRepository class and pass it an Amplify Auth Session object upon being instantiated, which it then stores for later use.
I'm trying to mock this out in my unit tests. After following the guides, I get this error:
_TypeError (type 'Null' is not a subtype of type 'Future<AuthSession>')
I'm not sure what I'm doing wrong, it seems as though my mock and test are correct but the UserRepository isn't using the mock.
Here is my class:
Here are my test an mock:
The text was updated successfully, but these errors were encountered: