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

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.

Related

Bdd test in 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.

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

Return type 'Null' is not a subtype of type 'Future<void>' in mocktail

I am trying to test cash articles with HIVE package put it throws a type 'Null' is not a subtype >of type 'Future
I stubed it but I don't know why
test:
group("cache last gotten articles", () {
test('should cache the last gotten articles', () async {
// arrange
final expectedJsonArticles = jsonEncode(fixture('cached_articles'));
when(() => mockHive.openBox(articles))
.thenAnswer((_) async => mockHiveBox);
when(() => mockHiveBox.put(articles, expectedJsonArticles)).thenAnswer((_) async =>true);
print(mockHiveBox.put(articles, expectedJsonArticles).runtimeType) ;
final x = mockHiveBox.put(articles, expectedJsonArticles);
// act
await articleLocaleDataSourceImpl.cacheArticleLocale(tArticlesList);
// assert
verify(()=>x).called(1);
verify(() => mockHive.openBox(articles)).called(1);
});
});
function:
Future<void> cacheArticleLocale(List<ArticleEntity> articles) async {
final box = await hive.openBox(LocaleDbKeys.articleBox);
final Map<String, dynamic> parsedArticles = {};
parsedArticles['articles'] =
articles.map((article) => (article as ArticleModel).toJson()).toList();
box.put(
LocaleDbKeys.articleBox, jsonEncode(parsedArticles['articles']));
}
I solve it...
the problem was with the data I put on put expectedJsonArticles on the test file and the data on the production file
mockHiveBox.put(articles, expectedJsonArticles)
box.put(LocaleDbKeys.articleBox, jsonEncode(parsedArticles['articles']));
is not the same
but the error message tells me that I didn't stub this!
in case you faced this error this will help you
another case
if you didn't stub a function it will return a null value instead
please check this:
https://pub.dev/packages/mocktail#:~:text=type%20%27Null%27%20is%20not%20a%20subtype%20of%20type%20%27Future%3Cvoid%3E%27

Flutter Test Error - type 'Null' is not a subtype of type 'Future<Response>'

I am trying to create a simple test but I keep getting this error.
type 'Null' is not a subtype of type 'Future'
test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:async/async.dart';
import 'package:http/http.dart' as http;
import 'package:mocktail/mocktail.dart';
class MockClient extends Mock implements http.Client {}
void main() {
group('signin', () {
final client = MockClient();
final api = AuthApi('https://baseUrl', client);
final credential = Credential(
email: 'test#test.com',
type: AuthType.email,
password: 'pass',
);
test('should return error when status code is not 200', () async {
registerFallbackValue(Uri.parse(''));
when(() => client.post(any(), body: {}))
.thenAnswer((_) async => http.Response('{}', 404));
final result = await api.signIn(credential);
expect(result, isA<ErrorResult>());
});
});
}
Error is at line
final result = await api.signIn(credential); expect(result,
isA());
If I remove those lines I don't see the error.
auth_api.dart
class AuthApi implements IAuthApi {
AuthApi(this.baseUrl, this._client);
final http.Client _client;
String baseUrl;
#override
Future<Result<String>> signIn(Credential credential) async {
final endpoint = Uri.parse(baseUrl + '/auth/signin');
return await _postCredential(endpoint, credential);
}
#override
Future<Result<String>> signUp(Credential credential) async {
final endpoint = Uri.parse(baseUrl + '/auth/signup');
return await _postCredential(endpoint, credential);
}
Future<Result<String>> _postCredential(
Uri endpoint,
Credential credential,
) async {
final response =
await _client.post(endpoint, body: Mapper.toJson(credential));
if (response.statusCode != 200) {
return Result.error('Server Error');
}
var json = jsonDecode(response.body);
return json['auth_token'] != null
? Result.value(json['auth_token'])
: Result.error(json['message']);
}
}
I checked other similar question answers also but none of them worked. I am using mocktail package & http for post.
The problem is in that line:
when(() => client.post(any(), body: {}))
.thenAnswer((_) async => http.Response('{}', 404));
It means that when there's a client.post() method invoked with any() URL and a specific empty body {}, then it should return a mocked response.
What you want is to return a mocked response when there's any URL and any body, so it should be like this:
when(() => client.post(any(), body: any(named: 'body')))
.thenAnswer((_) async => http.Response('{}', 404));
However, if you want to test if a specific error is thrown, that code should be modified:
test('should return error when status code is not 200', () async {
when(() => client.post(any(), body: any(named: 'body')))
.thenThrow(ErrorResult(Exception()));
expect(() async => await api.signIn(credential),
throwsA(isA<ErrorResult>()));
});
First, you specify that calling API should throw an error (when(...).thenThrow(...)) and then you check if an error was thrown (expect(..., throwsA(...)))

Flutter Unit Test with Future.then

I want to write a flutter unit test, where a future maps a result to something different and returns the new result again as future, like:
Future<String> invoke() => class.doSomething().then((value) => map(value));
The sample method "invoke()" should return the mapped value.
The positive test, returning the mapped value, is working. (test1 in sample below)
The negative test, returning an Future.error, fails with this error (test 2 in sample below):
Invalid argument(s) (onError): The error handler of Future.catchError
must return a value of the future's type
Does anybody know how to improve the test so i can test the error case like described in test2:
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import 'test_test.mocks.dart';
class A {
final B b;
A(this.b);
Future<String> doSomething() => b.doSomething().then((value) => "mappedResult");
}
class B {
Future<int> doSomething() => Future.delayed(const Duration(seconds: 1));
}
#GenerateMocks([B])
void main() {
final bmock = MockB();
test('test1', () async {
when(bmock.doSomething()).thenAnswer((_) => Future.value(1));
var result = await A(bmock).doSomething();
expect(result, "mappedResult");
});
test('test2', () async {
when(bmock.doSomething()).thenAnswer((_) => Future.error("error"));
A(bmock).doSomething().catchError((error) {
expect(error, "error");
});
});
}
Versions:
Flutter 2.10.1
mockito: ^5.0.17
test: ^1.17.12
Future<T>.catchError expects a callback that returns a Future<T>.
However, Future.catchError is confusing to use, and it'd be better to avoid it entirely:
try {
await A(bmock).doSomething();
fail('doSomething did not throw');
} catch (e) {
expect(e, 'error');
}
or you can use expectLater:
await expectLater(A(bmock).doSomething(), throwsA('error'));