Object/factory with type GroupProvider is already registered inside GetIt - flutter

Writing an integration test for an application. An error occurred while running the test:
As I understood from the debugger, my SetUp initializes my variables twice. That is, the variables are initialized, the first test is executed, and then SetUp is initialized again before executing the 2nd test. How can this problem be solved?
My Test:
class MockGroupProvider extends Mock implements GroupProvider {}
class MockStudentProvider extends Mock implements StudentProvider {}
class MockGroupRepository extends Mock implements GroupRepository {}
class MockDatabase extends Mock implements ObjectBox {}
void main() {
final injector = GetIt.instance;
final provider = MockGroupProvider();
final studentProvider = MockStudentProvider();
final repository = MockGroupRepository();
final db = MockDatabase();
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUp(() {
injector.registerSingleton<GroupProvider>(provider);
injector.registerSingleton<StudentProvider>(studentProvider);
injector.registerSingleton<GroupRepository>(repository);
injector.registerSingleton<ObjectBox>(db);
});
testWidgets(
"Not inputting a text and wanting to save group display an error: "
"Group name cannot be empty",
(WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(home: AddGroupPage()));
const IconData iconBtn = Icons.save;
final saveGroupBtn = find.byIcon(iconBtn);
await tester.tap(saveGroupBtn);
await tester.pumpAndSettle();
expect(find.byType(AddGroupPage), findsOneWidget);
expect(find.byType(GroupsPage), findsNothing);
expect(find.text('Group name cannot be empty'), findsOneWidget);
},
);
testWidgets(
"After inputting a text, go to the display page which contains group that same text ",
(WidgetTester tester) async {
await tester.pumpWidget(const MyApp());
const inputText = 'Group 1';
await tester.enterText(
find.byKey(const Key('add_group_field')), inputText);
const IconData iconBtn = Icons.save;
final saveGroupBtn = find.byIcon(iconBtn);
await tester.tap(saveGroupBtn);
await tester.pumpAndSettle();
expect(find.byType(AddGroupPage), findsNothing);
expect(find.byType(GroupsPage), findsOneWidget);
expect(find.text(inputText), findsOneWidget);
},
);
}

The setUp callback runs before each test, therefore it's registering your instances in GetIt multiple times, which will cause an exception to be thrown.
In your case, GetIt should not be necessary here, since no dependency injection appears necessary for your mocks. Instead, you can simply create a new instance of each of your dependencies before each test:
void main() {
late MockGroupProvider provider;
late MockStudentProvider studentProvider;
late MockGroupRepository repository;
late MockDatabase db;
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// Before each test, create new instances for each dependency
setUp(() {
provider = MockGroupProvider();
studentProvider = MockStudentProvider();
repository = MockGroupRepository();
db = MockDatabase();
});
// Tests...
}

Related

Flutter: How to unit testing moor/drift?

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>())
})
});
}

Flutter Testing LateInitializationError in my class?

I have a class that helps me handle the sharedpreferences:
class SharedPref {
static late final SharedPreferences prefs;
static initialize() async {
prefs = await SharedPreferences.getInstance();
}
SharedPref._();
static Future<void> save(String key, String value) async {
await prefs.setString(key, value);
}
// more methods that help me to save/load values...
}
Then I have a testing function:
class MockNavigatorObserver extends Mock implements NavigatorObserver {}
void main() {
testWidgets('Button is present and triggers navigation after tapped',
(WidgetTester tester) async {
final mockObserver = MockNavigatorObserver();
await tester.pumpWidget(
MaterialApp(
home: FirstPage(),
navigatorObservers: [mockObserver],
),
);
expect(find.byType(RaisedButton), findsOneWidget);
await tester.tap(find.byType(RaisedButton));
await tester.pumpAndSettle();
expect(find.byType(MyDetailedPage), findsOneWidget);
});
}
Whenever I run the test:
The following LateError was thrown running a test:
LateInitializationError: Field 'prefs' has not been initialized.
How am I supposed to initialize that? I called the SharedPref.initialize(); before the final mockObserver... but made no change.
Thanks in advance.
I called the SharedPref.initialize(); before
You need to await the call, otherwise you have race conditions when your save is called and initalize may or may not already be finished.
As you mentioned problem was in
final mockObserver = MockNavigatorObserver();
not inside main() function.
But it was another silly mistake with this kind error if wright initialisation like this:
late final MockNavigatorObserver mockObserver;
setUp(() {
mockObserver= MockNavigatorObserver();
})
Problem in this case is using final keyword. If use like that, only one test will pass. You need not using final keyword, instead:
late MockNavigatorObserver mockObserver;
setUp(() {
mockObserver= MockNavigatorObserver();
})

Flutter Unit Test for a Firebase Function

I'm brand new to flutter and I want to try to make a Unit Test for one functions that I've created. The function simply test if the email & password that the user provides to login are correct or not. So, the function provides a connection to the database and verify if the email & password are valid.
I tried to make a Unit Test with mockito to emulate the Firebase but it don't work very well. My function return an "UserCredential" type and I don't know how to verify it with Mocks. I tried to create a Mock for this type, but it says that "type 'Null' is not a subtype of type 'Future'"...
Anyone can make a clear explanation ? :)
Firebase Function to test
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flexmes/data/models/user_model.dart';
class UserAPI{
final CustomUser _customUser = CustomUser();
final FirebaseAuth auth;
UserAPI({required this.auth});
Future<UserCredential?> signInWithEmailAndPassword(String email, String password) async {
try{
UserCredential result = await auth.signInWithEmailAndPassword(email: email, password: password);
print (result);
return result;
} catch (error){
print (error);
return null;
}
}
}
Unit Test for the signInWithEmailAndPassword function
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flexmes/data/data_providers/user_provider.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
class MockFirebaseAuth extends Mock implements FirebaseAuth{}
class MockUserCredential extends Mock implements UserCredential{}
void main(){
final MockFirebaseAuth mockAuth = MockFirebaseAuth();
final MockUserCredential userCredential = MockUserCredential();
group('description', () {
late UserAPI userAPI;
setUp(() {
userAPI = UserAPI(auth: mockAuth);
});
tearDown(() {
});
test('Sign in with Email & Password', () async {
when(mockAuth.signInWithEmailAndPassword(email: "admin#admin.com", password: "password")).
thenAnswer((_) => Future<MockUserCredential>.value(userCredential));
expect(await userAPI.signInWithEmailAndPassword("admin#admin.com", "password"), userCredential);
});
});
}
There is mock authentication package available firebase_auth_mocks and google_sign_in_mocks
You can replace your code like this
group('description', () {
final _mockAuth = MockFirebaseAuth(mockUser: _mockUser);
late UserAPI userAPI;
setUp(() {
userAPI = UserAPI(auth: _mockAuth);
});
tearDown(() {});
const _email = 'ilyas#yopmail.com';
const _uid = 'sampleUid';
const _displayName = 'ilyas';
const _password = 'Test#123';
final _mockUser = MockUser(
uid: _uid,
email: _email,
displayName: _displayName,
);
test('signIn function test', () async {
final user = await userAPI.signInWithEmailAndPassword(_email, _password);
expect(user?.uid, _mockUser.uid);
});
});
google_sign_in_mocks this you can use for GoogleSignIn test

Flutter Integration testing failed for multiple test cases in a single file

I have a simple login page with email and password text field. A button to login and another to sign up. I tried to write integration testing for the Sign in page.
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('''could type email and password in text filed''',
(WidgetTester tester) async {
await app.main();
await tester.pumpAndSettle();
final textFieldEmail = find.byType(InputTextWidget).first;
final textFieldPassword = find.byType(InputTextWidget).last;
await tester.enterText(textFieldEmail, "asis.adh#gmail.com");
await tester.pumpAndSettle();
expect(find.text("asis.adh#gmail.com"), findsOneWidget);
});
testWidgets(
'should redirect to Sign Up Page when create an account is tapped',
(WidgetTester tester) async {
await app.main();
await tester.pumpAndSettle();
final createAnAccount = find.text("Create an account");
await tester.tap(createAnAccount);
await tester.pumpAndSettle();
expect(find.byType(SignupPage), findsOneWidget);
expect(find.byType(LoginPage), findsNothing);
});
}
When I execute the test case, it fails with the following error:
The following ArgumentError was thrown running a test: Invalid
argument(s): Object/factory with type AmenitiesProvider is already
registered inside GetIt.
Here is my main.dart file
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
configureInjection(Environment.prod);
/// for registering the factory.
await Future.delayed(const Duration(seconds: 2));
runApp(RoopaApp());
}
I tried with a main_test.dart and adding configureInjection(Environment.test) but nothing changes. I am not sure how to fix the error. Is there a way to clean the app or destroy it before going to new test case. If I combine the both testcase into one then it works without any problem.
Here is configureInjection
#injectableInit
void configureInjection(String environment) {
$initGetIt(getIt, environment: environment);
}
I am using get_it and injection package for dependency injection.
Here is the auto generated initGetIt
GetIt $initGetIt(
GetIt get, {
String environment,
EnvironmentFilter environmentFilter,
}) {
final gh = GetItHelper(get, environment, environmentFilter);
final httpClientInjectableModule = _$HttpClientInjectableModule();
final flutterStorageModule = _$FlutterStorageModule();
gh.lazySingleton<AmenitiesProvider>(() => AmenitiesProvider());
gh.factory<Client>(() => httpClientInjectableModule.client);
gh.lazySingleton<FileProvider>(() => FileProvider());
gh.lazySingleton<FlutterSecureStorage>(
() => flutterStorageModule.secureStorate);
gh.factory<ProfilePageBloc>(() => ProfilePageBloc());
gh.factory<SplashScreenBloc>(() => SplashScreenBloc());
gh.lazySingleton<AuthLocalDataSourceProtocol>(
() => AuthLocalDataSource(secureStorage: get<FlutterSecureStorage>()));
gh.lazySingleton<AuthRemoteDataSourceProtocol>(
() => AuthRemoteDataSource(client: get<Client>()));
return get;
}
In my config page.
#injectableInit
void configureInjection(String environment) {
$initGetIt(getIt, environment: environment);
}
I just created a test environment and added following like, now its working as expected.
#injectableInit
void configureInjection(String environment) {
$initGetIt(getIt, environment: environment);
if (environment == Environment.test) {
getIt.allowReassignment = true;
}
}
tearDown(() async {
final getIt = GetIt.instance;
await getIt.reset();
});

Box not found. Did you forget to call Hive.openBox()?

When working with HIVE database in flutter. If you ever get error like this:
"Box not found. Did you forget to call Hive.openBox()?"
It means you haven't opened your box to
To resolve this issue call
await Hive.openBox("boxname");
before using the box
It means you haven't opened your box. To resolve this issue call
await Hive.openBox("boxname");
before using the box.
The box needs to be open either at the beginning, after database initialization or right before doing the operation on the box.
For example in my AppDatabase class I have only one box ('book') and I open it up in the initialize() method, like below:
The whole application and tutorial is here.
const String _bookBox = 'book';
#Singleton()
class AppDatabase {
AppDatabase._constructor();
static final AppDatabase _instance = AppDatabase._constructor();
factory AppDatabase() => _instance;
late Box<BookDb> _booksBox;
Future<void> initialize() async {
await Hive.initFlutter();
Hive.registerAdapter<BookDb>(BookDbAdapter());
_booksBox = await Hive.openBox<BookDb>(_bookBox);
}
Future<void> saveBook(Book book) async {
await _booksBox.put(
book.id,
BookDb(
book.id,
book.title,
book.author,
book.publicationDate,
book.about,
book.readAlready,
));
}
Future<void> deleteBook(int id) async {
await _booksBox.delete(id);
}
Future<void> deleteAllBooks() async {
await _booksBox.clear();
}
}
You have to open the box you want to use and make sure to use await while using the openBox() function.
await Hive.openBox("boxname");