Bdd test in flutter - flutter

I want to set some simple BDD tests for my app and I use Dependency injection and Hive in my flutter as well.
I use this package
This is the BDD test
Feature: Counter
Scenario: Initial counter value is 0
Given the app is running
Then I see {'Empty'} text
and this is the main function for the test
main() {
group('''Counter''', () {
testWidgets('''Initial counter value is 0''', (tester) async {
await theAppIsRunning(tester);
await iSeeText(tester, 'Empty');
});
});
}
when I run this it returns getIt errors
this is my getIt locator
final locator = GetIt.instance;
Future<void> setup() async {
initFeatures();
await initExternal();
}
void initFeatures() {
// Cubit
locator
.registerLazySingleton<UserCubit>(() => UserCubit(locator(), locator()));
locator.registerLazySingleton<AddEditCubit>(
() => AddEditCubit(locator(), locator()));
// use Cases
locator.registerLazySingleton(() => CreateUserUseCase(locator()));
locator.registerLazySingleton(() => EditUserUseCase(locator()));
locator.registerLazySingleton(() => DeleteUserUseCase(locator()));
locator.registerLazySingleton(() => GetUsersUseCase(locator()));
// repository
locator.registerLazySingleton<UserRepository>(
() => UserRepositoryImpl(userLocalDataSource: locator()));
// dataSource
locator.registerLazySingleton<UserLocalDataSource>(() =>
UserLocalDataSourceImpl(box: locator(), inputDataValidator: locator()));
locator.registerLazySingleton<InputDataValidator>(
() => InputDataValidatorImpl(box: locator()));
}
// hive injection
Future<void> initExternal() async {
final Box box = await Hive.openBox("user");
locator.registerLazySingleton<Box>(() => box);
}
how can I run my tests with all this?I should initialize getIt injector and Hive as well.

Related

FakeUsedError: 'execute' No Stub was found

I'm using mockito for testing, riverpod for state management. I'm trying to test the method in my controller class but getting the FakeUsedError:
FakeUsedError: 'execute' No stub was found which matches the argument
of this method call: execute(Instance of 'AuthUseCaseInput').
I'm calling AuthUseCase class method from the AuthController class.
class AuthController extends StateNotifier<AuthState> {
final AuthUseCase authUseCase;
AuthController(this.authUseCase) : super(const AuthState.initial());
Future<void> mapAuthEventToAuthState(AuthEvent event) async {
state = const AuthState.loading();
await event.map(
signInWithEmailAndPassword: (signInWithEmailAndPassword) async {
final result = await authUseCase.execute(AuthUseCaseInput(
signInWithEmailAndPassword.email,
signInWithEmailAndPassword.password));
await result.fold(
(failure) async => state = AuthState.error(failure),
(login) async => state = const AuthState.loggedIn(),
);
});
}
The test class code is given below
void main() {
late AuthUseCase mockAuthUseCase;
late Login login;
late AuthUseCaseInput authUseCaseInput;
late AuthController authController;
setUpAll(() {
mockAuthUseCase = MockAuthUseCase();
login = LoginModel.fromJson(
json.decode(
jsonReader('helpers/dummy_data/login_success_response.json'),
),
).toEntity();
authUseCaseInput = AuthUseCaseInput(email, password);
when(mockAuthUseCase.execute(authUseCaseInput)).thenAnswer(
(_) async => Right(login),
);
authController = AuthController(mockAuthUseCase);
});
group('Auth Controller', () {
stateNotifierTest<AuthController, AuthState>(
'[AuthState.loggedIn] when sign in is success',
setUp: () async {
when(mockAuthUseCase.execute(authUseCaseInput))
.thenAnswer(
(_) async => Right(login),
);
},
actions: (notifier) => notifier.mapAuthEventToAuthState(
const SignInWithEmailAndPassword(email, password)),
expect: () => [const AuthState.loading(), const AuthState.loggedIn()],
build: () {
return authController;
});
});
}

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

Problems at Flutter/Dart Unit Test development

I'm trying to code the Unit Test for my DataSourceImpl class, but I'm facing a problem with the methods that are called into the testing method.
I mocked and set the return for all methods into the DataSource testing method with the when statement, but it's not working...
Into the save and remove methods the Stream is called to notify the changes.
I'm using mocktail: ^0.1.4 for mocking dependency objects.
DATASOURCE IMPLEMENTATION
class DocumentDataSourceImpl implements IDocumentDataSource {
final FlutterSecureStorage _storage;
final StreamController<List<DocumentModel>> _streamController;
DocumentDataSourceImpl(this._storage, this._streamController);
#override
Future<void> remove(String id) async {
try {
await _storage.delete(key: id);
_streamController.sink.add(await getAll());
} catch (e) {
throw DocumentNotRemovedException();
}
}
#override
Future<void> save(DocumentModel model) async {
try {
await _storage.write(key: model.id, value: json.encode(model.toJson()));
_streamController.sink.add(await getAll());
} catch (e) {
throw DocumentNotSavedException();
}
}
#override
Future<List<DocumentModel>> getAll() async {
final result = await _storage.readAll();
return result.entries
.map((e) => DocumentModel.fromJson(json.decode(e.value)))
.toList();
}
}
UNIT TEST
void main() {
// Global Definitions
//-----Main Objects
late FlutterSecureStorageMock storage;
late StreamControllerMock<List<DocumentModel>> streamController;
late DocumentDataSourceImpl dataSource;
// SetUp Test
setUp(() {
//-----Objects Initialization
storage = FlutterSecureStorageMock();
streamController = StreamControllerMock();
dataSource = DocumentDataSourceImpl(storage, streamController);
});
group(("DocumentDataSource: Remove"), () {
final tArgument = "Document 1";
test("[P] Should remove the DocumentModel on Storage", () async {
//-----Arrange
when(() => storage.readAll())
.thenAnswer((_) async => tDocumentModelListMap);
when(() => dataSource.getAll())
.thenAnswer((_) async => tDocumentModelList);
when(() => streamController.sink.add(tDocumentModelList));
when(() => storage.delete(key: tArgument)).thenAnswer((_) async => null);
//-----Act
dataSource.remove(tArgument);
//-----Assert
verify(() => storage.delete(key: tArgument)).called(1);
verify(() => streamController.sink.add(tDocumentModelList)).called(1);
});
test(
"[N] Should return a DocumentNotRemovedException when the call of Storage is unsuccessful.",
() async {
//-----Arrange
when(() => storage.delete(key: tArgument)).thenThrow(
GenericExceptionMock(),
);
//-----Act
final result = dataSource.remove(tArgument);
//-----Assert
expect(result, throwsA(DocumentNotRemovedException()));
verify(() => storage.delete(key: tArgument)).called(1);
verifyNever(() => streamController.sink.add(tDocumentModelList));
});
});
group(("DocumentDataSource: Save"), () {
final tArgument = tDocumentModel;
test("[P] Should save the DocumentModel on Storage", () async {
//-----Arrange
when(() => storage.readAll())
.thenAnswer((_) async => tDocumentModelListMap);
when(() => dataSource.getAll())
.thenAnswer((_) async => tDocumentModelList);
when(() => streamController.sink.add(tDocumentModelList));
when(() => storage.write(
key: tArgument.id, value: json.encode(tArgument.toJson())));
//-----Act
dataSource.save(tArgument);
//-----Assert
verify(() => storage.write(
key: tArgument.id, value: json.encode(tArgument.toJson()))).called(1);
// verify(() => realTimeEvents.update(tDocumentModelList)).called(1);
});
test(
"[N] Should return a DocumentNotSavedException when the call of Storage is unsuccessful.",
() async {
//-----Arrange
when(() => storage.write(
key: tArgument.id, value: json.encode(tArgument.toJson()))).thenThrow(
GenericExceptionMock(),
);
//-----Act
final result = dataSource.save(tArgument);
//-----Assert
expect(result, throwsA(DocumentNotSavedException()));
verify(() => storage.write(
key: tArgument.id, value: json.encode(tArgument.toJson()))).called(1);
verifyNever(() => streamController.sink.add(tDocumentModelList));
});
});
}
MOCKS
class FlutterSecureStorageMock extends Mock implements FlutterSecureStorage {}
class StreamControllerMock<T> extends Mock implements StreamController<T> {}
ERROR LOG
type 'Null' is not a subtype of type 'Future<Map<String, String>>'
FlutterSecureStorageMock.readAll
DocumentDataSourceImpl.getAll
main.<fn>.<fn>.<fn>
when.<fn>
main.<fn>.<fn>
main.<fn>.<fn>
===== asynchronous gap ===========================
dart:async _completeOnAsyncError
package:memo/features/data/datasources/document_datasource_impl.dart DocumentDataSourceImpl.getAll
main.<fn>.<fn>.<fn>
when.<fn>
main.<fn>.<fn>
main.<fn>.<fn>
Bad state: Cannot call `when` within a stub response
when
main.<fn>.<fn>
main.<fn>.<fn>
✖ DocumentDataSource: Remove [P] Should remove the DocumentModel on Storage
Bad state: Cannot call `when` within a stub response
when
main.<fn>.<fn>
main.<fn>.<fn>
✖ DocumentDataSource: Remove [N] Should return a DocumentNotRemovedException when the call of Storage is unsuccessful.
Bad state: Cannot call `when` within a stub response
when
main.<fn>.<fn>
main.<fn>.<fn>
✖ DocumentDataSource: Save [P] Should save the DocumentModel on Storage
Bad state: Cannot call `when` within a stub response
when
main.<fn>.<fn>
main.<fn>.<fn>
✖ DocumentDataSource: Save [N] Should return a DocumentNotSavedException when the call of Storage is unsuccessful.
Exited (1)

Dart: How to mock and stub Sqflite transaction (inner callback)?

i am trying to mock the following method of sqlite_api.dart by (https://pub.dev/packages/sqflite):
Future<T> transaction<T>(Future<T> Function(Transaction txn) action, {bool? exclusive});
my implementation/adapting of the method is like:
Future<void> _transaction(Set<DatabaseLocalRequest> payload) async {
await this._api.transaction((txn) async => {
for (final req in payload) {
await txn.rawInsert(req.query.sql, req.query.arguments)
}
});
}
my db_test.dart using Mocktail (https://pub.dev/packages/mocktail):
test('if [single] put succeeds', () async {
// SETUP
sut = DatabaseLocalProvider(db: mockDb);
final query = Statement(sql: 'INSERT INTO Test(name, value, num) VALUES("some name", 1234, 456.789)');
final req = DatabaseLocalRequest(query: query);
// MOCK
when(() => mockDb.transaction((txn) => txn.rawInsert(req.query.sql, req.query.arguments)))
.thenAnswer((_) async => 1);
// ACT, ASSERT
await sut.put(req: req, bulkReq: null).then((response) => {
expect(response, ...
});
}); // test end
I got the following response from the console ERROR:
🚨🚨
type 'Null' is not a subtype of type 'Future<Set<Set<int>>>'
How do I stub the inner txn.rawInsert() method that should respond with the Future<Set<Set<int>>> with {{1}}?
Thanks in advance!
I might not respond exactly to your question but you can mock sqflite by using a real implementation with sqflite_common_ffi since it works on all desktop (MacOS, Linux, Windows) on the dart VM so also in flutter and dart unit tests:
More information here: https://pub.dev/packages/sqflite_common_ffi#unit-test-code
One solution is open a database in memory for each test so that you start with an empty database.
import 'package:test/test.dart';
import 'package:sqflite_common/sqlite_api.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
void main() {
// Init ffi loader if needed.
sqfliteFfiInit();
test('simple sqflite example', () async {
var db = await databaseFactoryFfi.openDatabase(inMemoryDatabasePath);
expect(await db.getVersion(), 0);
await db.close();
});
}
when(() => mockDb.transaction(any())).thenAnswer((_) async => {{1}});
when(() => mockDb.rawInsert(any())).thenAnswer((_) async => 1);
this did the trick! but it is not 100 solution, because the closure is not stubbed but bypassed.

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