I am trying to test my catch in my signInAnonyymous() function. Yet, I am constantly getting the error stated in the title.
Here is my Mocked Auth class (edited for brevity):
class MockFirebaseAuth extends Mock implements FirebaseAuth {
final bool signedIn;
final bool isEmployee;
MockFirebaseAuth({this.isEmployee = false, this.signedIn = false});
#override
Future<UserCredential> signInAnonymously() async {
return MockUserCredential(isAnonymous: true);
}
}
And, this is my test:
group('Sign-In-Anonymously', () {
setUp(() {
mockFunctions = MockFirebaseFunctions();
mockAuth = MockFirebaseAuth();
mockCrashltyics = MockCrashlytics();
mockFirestore = MockFirebaseFirestore();
authRemoteService = AuthServiceFirebase(
crashlytics: mockCrashltyics,
firestoreService: mockFirestore,
functions: mockFunctions,
service: mockAuth,
);
authProvider = AuthProvider(authRemoteService);
});
test(
'Should return a null AuthUser and non-null errorMessage on exception thrown',
() async {
when(mockAuth.signInAnonymously())
.thenThrow(MockFirebaseAuthException('message', 'code'));
//act
await authProvider.signInAnonymously();
//assert
expect(authProvider.authUser, isNull);
expect(authProvider.errorMessage, isNotNull);
});
});
I have researched and tried so many different ways and still can't test my catch. Any help would be appreciated. Thank you in advance.
If you're going to use 'when', you can't override the method in the mock. You need to provide an answer from the "thenAnswer".
Related
I'm writing unit tests to test the authorise function below which is in AuthService class.
Future<bool> authorize() async {
AuthorizationTokenResponse? authResult;
try {
authResult = await appAuth.authorizeAndExchangeCode(
AuthorizationTokenRequest(
bsEnv.AD_CLIENT_ID!,
));
if (authResult != null) {
return true;
}
} on PlatformException catch (e) {
forgetPassword();
}
}
return false;
}
I am using Mocktail to make the mocks. Below is one written test for the authorise function mentioned in the above code.
class MockBEnv extends Mock implements BEnv {}
class MockSharedPrefsService extends Mock implements SharedPrefsService {}
class MockFlutterAppAuth extends Mock implements FlutterAppAuth {}
class MockAuthorizationServiceConfiguration extends Mock
implements AuthorizationServiceConfiguration {}
class MockAuthorizationTokenResponse extends Mock
implements AuthorizationTokenResponse {}
class MockAuthorizationTokenRequest extends Mock
implements AuthorizationTokenRequest {}
class MockTokenResponse extends Mock implements TokenResponse{}
class MockTokenRequest extends Mock implements TokenRequest{
}
void main() {
late AuthService authService;
late MockFlutterAppAuth mockFlutterAppAuth;
late MockBEnv mockBEnv;
late MockSharedPrefsService mockStorageService;
late MockAuthorizationTokenResponse mockAuthorizationTokenResponse;
late MockAuthorizationTokenRequest mockAuthorizationTokenRequest;
const tUrl = 'https://endpoint/';
const tUserImp = 'user';
const tAppLan = 'sv-SE';
const tClientId = 'client';
const tIosUri = 'ios';
const tTokenEndPoint = 'abcdeff';
setUp(() {
mockFlutterAppAuth = MockFlutterAppAuth();
mockBEnv = MockBEnv();
mockStorageService = MockSharedPrefsService();
mockAuthorizationTokenRequest = MockAuthorizationTokenRequest();
mockAuthorizationTokenResponse = MockAuthorizationTokenResponse();
authService = AuthService(
appAuth: mockFlutterAppAuth,
bsEnv: mockBEnv,
sharedPrefs: mockStorageService);
});
group('authorize', () {
test(
'should return true when the call to authorized method if result is not null',
() async {
// Arrange
when(() => mockBEnv.AD_CLIENT_ID).thenAnswer((_) => tClientId);
when(() => mockFlutterAppAuth
.authorizeAndExchangeCode(mockAuthorizationTokenRequest))
.thenAnswer((_)=> Future.value(mockAuthorizationTokenResponse));
// Act
final result = authService.authorize;
// Assert
expect(() => result(), equals(false));
});
});
}
My test fails and below is the error message I get,
Expected: < false >
Actual: <Closure: () => Future>
How can I fix this issue?
authService.authorize is an asynchronous method, returning a Future<bool>.
So your testcase should be
final result = await authService.authorize();
expect(result, equals(false));
The await keyword leads to an async execution that resolves the Future into an actual value
my test is throwing an exception because there is a StateNotifierProvider inside which is not overridden. for a regular Provider, i can override it using providerContainer, but for the state of a stateNotifierProvider, I don't know how to do it. I tried my best but I reached the limit of my best. I already saw this and this but it didn't help.
Appreciate much if someone could help me out of this. Thanks
My service File
class ReportService {
final Ref ref;
ReportService({
required this.ref,
});
Future<void> testReport() async {
//* How can i override this provider ?
final connection = ref.read(connectivityServiceProvider);
if (connection) {
try {
await ref.read(reportRepositoryProvider).testFunction();
} on FirebaseException catch (e, st) {
ref.read(errorLoggerProvider).logError(e, st);
throw Exception(e.message);
}
} else {
throw Exception('Check internet connection...');
}
}
}
final reportServiceProvider = Provider<ReportService>((ref) => ReportService(
ref: ref,
));
My test file
void main() {
WidgetsFlutterBinding.ensureInitialized();
final reportRepository = MockReportRepository();
ReportService makeReportService() {
final container = ProviderContainer(overrides: [
reportRepositoryProvider.overrideWithValue(reportRepository),
]);
addTearDown(container.dispose);
return container.read(reportServiceProvider);
}
test('test test', () async {
//How to stub the connectivityServiceProvider here ?
when(reportRepository.testFunction)
.thenAnswer((invocation) => Future.value());
final service = makeReportService();
await service.testReport();
verify(reportRepository.testFunction).called(1);
});
My StateNotifierProvider
class ConnectivityService extends StateNotifier<bool> {
ConnectivityService() : super(false);
}
final connectivityServiceProvider =
StateNotifierProvider<ConnectivityService, bool>(
(ref) => ConnectivityService());
I already implementation of Drift for local storage, and want make it testable function. But I get stack and idk how to fix it the unit test.
HomeDao
#DriftAccessor(tables: [RepositoriesTable])
class HomeDao extends DatabaseAccessor<AppDatabase> with _$HomeDaoMixin {
HomeDao(AppDatabase db) : super(db);
Future<List<RepositoriesTableData>> getRepositories() async =>
await select(repositoriesTable).get();
}
AppDatabase
#DriftDatabase(
tables: [RepositoriesTable],
daos: [HomeDao],
)
class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection());
#override
int get schemaVersion => 1;
}
QueryExecutor _openConnection() {
return SqfliteQueryExecutor.inDatabaseFolder(
path: 'db.sqlite',
logStatements: true,
);
}
LocalDataSources
abstract class GTHomeLocalDataSource {
const GTHomeLocalDataSource();
Future<List<RepositoriesTableData>> getRepositories();
}
class GTHomeLocalDataSourceImpl implements GTHomeLocalDataSource {
final AppDatabase appDatabase;
const GTHomeLocalDataSourceImpl({required this.appDatabase});
#override
Future<List<RepositoriesTableData>> getRepositories() async =>
await appDatabase.homeDao.getRepositories();
}
UnitTesting
void main() => testGTHomeLocalDataSource();
class MockDatabaseHandler extends Mock implements AppDatabase {}
void testGTHomeLocalDataSource() {
late GTHomeLocalDataSource localDataSource;
late AppDatabase databaseHandler;
setUp(() {
databaseHandler = MockDatabaseHandler();
localDataSource = GTHomeLocalDataSourceImpl(
appDatabase: databaseHandler,
);
});
group("GTHomeLocalDataSource -", () {
test(''' \t
GIVEN Nothing
WHEN call getRepositories
THEN databaseHandler select function has been called and return list of RepositoriesTableData
''', () async {
// GIVEN
when(() => databaseHandler.homeDao.getRepositories())
.thenAnswer((_) => Future.value(repositoriesDummyTable));
// WHEN
final result = await localDataSource.getRepositories();
// THEN
verify(() => databaseHandler.homeDao.getRepositories());
expect(result, isA<List<RepositoriesTableData>>());
expect(result.length, repositoriesDummyTable.length);
expect(result.first.language, repositoriesDummyTable.first.language);
});
});
tearDown(() async {
await databaseHandler.close();
});
}
My function is work well for get data from the local db and show it in the app, but when running as unit test, I stacked with this error.
package:gt_core/local/database/database_module.g.dart 424:22 MockDatabaseHandler.homeDao
package:gt_home/data/data_sources/gt_home_local_datasource.dart 20:25 GTHomeLocalDataSourceImpl.getRepositories
test/data/data_sources/gt_home_local_datasource_test.dart 35:44 testGTHomeLocalDataSource.<fn>.<fn>
test/data/data_sources/gt_home_local_datasource_test.dart 29:12 testGTHomeLocalDataSource.<fn>.<fn>
type 'Null' is not a subtype of type 'Future<void>'
package:drift/src/runtime/api/db_base.dart 125:16 MockDatabaseHandler.close
test/data/data_sources/gt_home_local_datasource_test.dart 47:27 testGTHomeLocalDataSource.<fn>
test/data/data_sources/gt_home_local_datasource_test.dart 46:12 testGTHomeLocalDataSource.<fn>
===== asynchronous gap ===========================
dart:async _completeOnAsyncError
test/data/data_sources/gt_home_local_datasource_test.dart testGTHomeLocalDataSource.<fn>
test/data/data_sources/gt_home_local_datasource_test.dart 46:12 testGTHomeLocalDataSource.<fn>
type 'Future<List<RepositoriesTableData>>' is not a subtype of type 'HomeDao'
Anyone know how to fix it?
If you use Mocking to test Drift database, then you'll need to mock the method call as well, otherwiser the method will return null which is the default behavior for Mockito. For example.
// return rowid
when(db.insertItem(any)).thenAnswer((_) => 1);
However it is recommended as per Drift documentation to use in-memory sqlite database which doesn't require real device or simulator for testing.
This issue was also has been discussed here
Using in memory database
import 'package:drift/native.dart';
import 'package:test/test.dart';
import 'package:my_app/src/database.dart';
void main() {
MyDatabase database;
MyRepo repo;
setUp(() {
database = MyDatabase(NativeDatabase.memory());
repo = MyRepo(appDatabase: database);
});
tearDown(() async {
await database.close();
});
group('mytest', () {
test('test create', () async {
await repo.create(MyDateCompanion(title: 'some name'));
final list = await repo.getItemList();
expect(list, isA<MyDataObject>())
})
});
}
I have the following repository and I'd like to test it. I know this may be a silly question but I'm still learning.
class AuthRepository implements AuthBaseRepository {
final Reader _read;
const AuthRepository(this._read);
#override
Future<User> login({String email, String password}) async {
try {
final response = await _read(dioProvider).post(
'/sign_in',
data: {
"user": {
"email": email,
"password": password,
},
},
);
return _mapUserFromResponse(response);
} on DioError catch (_) {
throw const CustomException(message: 'Invalid login credentials.');
} on SocketException catch (_) {
const message = 'Please check your connection.';
throw const CustomException(message: message);
}
}
And this is what I've done so far:
void main() {
test('loadUser', () async {
Dio dio;
DioAdapterMockito dioAdapterMockito;
AuthRepository repository;
setUpAll(() {
dio = Dio();
dioAdapterMockito = DioAdapterMockito();
dio.httpClientAdapter = dioAdapterMockito;
repository = AuthRepository(_reader_here_);
});
test('mocks any request/response via fetch method', () async {
final responsePayload =
await parseJsonFromAssets("assets/api-response.json");
final responseBody = ResponseBody.fromString(
responsePayload,
200,
headers: {
Headers.contentTypeHeader: [Headers.jsonContentType],
},
);
when(dioAdapterMockito.fetch(any, any, any))
.thenAnswer((_) async => responseBody);
});
});
}
I have no idea of how to mock Reader. Basically, I've seen something like class MyMock extends Mock implements Something but Reader is not a class, it's a function so I'm completely lost.
Any help/tips/examples will be appreciated.
Thanks in advance!
Instead of trying to mock a Reader, create a provider for your repository and use ProviderContainer to read it.
class AuthRepository implements AuthBaseRepository {
const AuthRepository(this._read);
static final provider = Provider<AuthRepository>((ref) => AuthRepository(ref.read));
final Reader _read;
#override
Future<User> login({String email, String password}) async {
...
}
Example usage:
final user = createTestUser();
final container = ProviderContainer(
overrides: [
// Example of how you can mock providers
dio.overrideWithProvider(mockDio),
],
);
final repo = container.read(AuthRepository.provider);
expectLater(
await repo.login(email: 'AzureDiamond', password: 'hunter2'),
user,
);
You could also consider using the overrides in ProviderContainer to mock Dio instead of involving a mocking framework to simplify your tests further.
More on testing here.
I'm trying to use riverpod for login with a laravel backend. Right now I'm just returning true or false from the repository. I've set a form that accepts email and password. The isLoading variable is just to show a circle indicator. I've run the code and it works but not sure if I'm using riverpod correctly. Is there a better way to do it ?
auth_provider.dart
class Auth{
final bool isLogin;
Auth(this.isLogin);
}
class AuthNotifier extends StateNotifier<Auth>{
AuthNotifier() : super(Auth(false));
void isLogin(bool data){
state = new Auth(data);
}
}
final authProvider = StateNotifierProvider((ref) => new AuthNotifier());
auth_repository.dart
class AuthRepository{
static String url = "http://10.0.2.2:8000/api/";
final Dio _dio = Dio();
Future<bool> login(data) async {
try {
Response response = await _dio.post(url+'sanctum/token',data:json.encode(data));
return true;
} catch (error) {
return false;
}
}
}
login_screen.dart
void login() async{
if(formKey.currentState.validate()){
setState((){this.isLoading = true;});
var data = {
'email':this.email,
'password':this.password,
'device_name':'mobile_phone'
};
var result = await AuthRepository().login(data);
if(result){
context.read(authProvider).isLogin(true);
setState((){this.isLoading = false;});
}else
setState((){this.isLoading = false;});
}
}
Since I'm not coming from mobile background and just recently use flutter+riverpod in my recent project, I cannot say this is the best practice. But there are some points I'd like to note:
Use interface such IAuthRepository for repository. Riverpod can act as a dependency injection.
final authRepository = Provider<IAuthRepository>((ref) => AuthRepository());
Build data to send in repository. You should separate presentation, business logic, and explicit implementation for external resource if possible.
Future<bool> login(String email, String password) async {
try {
var data = {
'email': email,
'password': password,
'device_name':'mobile_phone'
};
Response response = await _dio.post(url+'sanctum/token',data:json.encode(data));
return true;
} catch (error) {
return false;
}
}
Do not call repository directly from presentation/screen. You can use the provider for your logic, which call the repository
class AuthNotifier extends StateNotifier<Auth>{
final ProviderReference ref;
IAuthRepository _authRepository;
AuthNotifier(this.ref) : super(Auth(false)) {
_authRepository = ref.watch(authRepository);
}
Future<void> login(String email, String password) async {
final loginResult = await_authRepository.login(email, password);
state = Auth(loginResult);
}
}
final authProvider = StateNotifierProvider((ref) => new AuthNotifier(ref));
On screen, you can call provider's login method
login() {
context.read(authProvider).login(this.email, this.password);
}
Use Consumer or ConsumerWidget to watch the state and decide what to build.
It also helps that instead of Auth with isLogin for the state, you can create some other state. At the very least, I usually create an abstract BaseAuthState, which derives to AuthInitialState, AuthLoadingState, AuthLoginState, AuthErrorState, etc.
class AuthNotifier extends StateNotifier<BaseAuthState>{
...
AuthNotifier(this.ref) : super(AuthInitialState()) { ... }
...
}
Consumer(builder: (context, watch, child) {
final state = watch(authProvider.state);
if (state is AuthLoginState) ...
else if (state is AuthLoadingState) ...
...
})
Instead of using a bool, I like to use enums or class for auth state
enum AuthState { initialize, authenticated, unauthenticated }
and for login state
enum LoginStatus { initialize, loading, success, failed }