Flutter Unit Test with Future.then - flutter

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'));

Related

Dart: How to test a function that takes a function as a parameter?

i been trying to unit test a function that takes a function as a parameter the unit test returns null value on the function that im testing so may i ask how to unit test this kind of function in dart.
this is the function that i want to test
final result = await _appStateNotifier.guard(
() => _authService.requestTempPassword(username: credentials.username),
);
and this is how i test it but got an error type 'Null' is not a subtype of type 'Future<Result<ErrorObject, String>>'
when(() => mockAuthService.requestTempPassword(username: tCredentials.username))
.thenAnswer((_) async => successMessage);
when(() => mockStateNotifier.guard(
() => mockAuthService.requestTempPassword(username: tCredentials.username),
),
).thenAnswer((_) async => const Success(successMessage));
await notifier.onRequestTempPassword(credentials: tCredentials);
and this is the guard clause function
Future<Result<ErrorObject, T>> guard<T>(Future<T> Function() function) async {
try {
final data = await future();
return Success(data);
} on FailureException catch (e) {
return Error(e);
} catch (e, s) {
return Error(e);
}
}
thank you
Your Future<Result<ErrorObject, T>> excludes the possibility of having a Null result. If you want to allow Null, then you need to make it nullable, see https://dart.dev/null-safety/understanding-null-safety
I'm not fluent with Flutter, so the syntax might be off, but as far as I understand, you could change that to
Future<Result<ErrorObject, T>>?
in order to make it nullable. Let me know if I'm totally off with the syntax.
EDIT
It turns out that the solution finally applied was putting the when method in the setpup function before the test run, as #Ken Verganio described in the comment section.

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

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.

How to mock a future method and not get type 'Null' is not a subtype of type 'Future<>' in flutter

I want to mock this class and this specific method
class FeedApiService extends ApiService {
Future<FeedResponse> fetchFeed(Map<String, String> params) async {
...
}
...
}
My unit test is like this
class FeedApiServiceMock extends Mock implements FeedApiService {}
void main() {
test('..', () {
FeedApiServiceMock feedApiServiceMock = FeedApiServiceMock();
when(feedApiServiceMock.fetchFeed({})).thenAnswer(
(_) => Future.value(FeedResponse(items: 1)),
);
expect(await feedApiServiceMock.fetchFeed({}).items, 1);
});
}
I just want to see that fetch feed is mocked correctly, but I'm getting this error:
type 'Null' is not a subtype of type 'Future<FeedResponse>'
See if adding async in both the test and thenAnswer method solves the problem.
void main() {
test('..', () async{
FeedApiServiceMock feedApiServiceMock = FeedApiServiceMock();
when(feedApiServiceMock.fetchFeed({})).thenAnswer(
(_) async=> Future.value(FeedResponse(items: 1)),
);
expect(await feedApiServiceMock.fetchFeed({}).items, 1);
});
}