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

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

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 Riverpod - .autoDispose - The argument type 'AutoDisposeProvider' can't be assigned to the parameter type 'AlwaysAliveProviderBase'

According to the docs when I'm getting this error I am supposed to mark both Providers with .autoDispose:
The argument type 'AutoDisposeProvider' can't be assigned to the
parameter type 'AlwaysAliveProviderBase'
Why am I still getting the error in this minimalistic example?
final a = FutureProvider.autoDispose<List<String>>((ref) {
return Future.value(["test"]);
});
final b = FutureProvider.autoDispose<List<String>>((ref) {
return ref.watch(a);
});
This error is happening because you tried to listen to a provider marked with .autoDispose in a provider that is not marked with .autoDispose.
final firstProvider = Provider.autoDispose((ref) => 0);
final secondProvider = Provider((ref) {
// The argument type 'AutoDisposeProvider<int>' can't be assigned to the
// parameter type 'AlwaysAliveProviderBase<Object, Null>'
ref.watch(firstProvider);
});
The solution is marking the secondProvider with .autoDispose.
final firstProvider = Provider.autoDispose((ref) => 0);
final secondProvider = Provider.autoDispose((ref) {
ref.watch(firstProvider);
});
I just copy the solution from the official Riverpod documentation at the bottom of the page:
https://riverpod.dev/docs/concepts/modifiers/auto_dispose/
It's not because of the autoDispose. If you replace the code with the following code, you'll get an error again:
// Removed "autoDispose"
final a = FutureProvider<List<String>>((ref) {
return Future.value(["test"]);
});
final b = FutureProvider<List<String>>((ref) {
return ref.watch(a);
});
The Error:
The argument type 'FutureProvider<List<String>>' can't be assigned to the parameter type 'AlwaysAliveProviderListenable<FutureOr<List<String>>>'.
The reason is that value of the a provider is an AsyncValue so b provider should returns a AsyncValue<List<String>> instead of List<String> if it return the data directly.
final a = FutureProvider.autoDispose<List<String>>((ref) {
return Future.value(["test"]);
});
final b = FutureProvider.autoDispose<AsyncValue<List<String>>>((ref) {
return ref.watch(a);
});
Or it can use the value and process it and then returns another list based on that, something like this:
final a = FutureProvider.autoDispose<List<String>>((ref) {
return Future.value(["test"]);
});
final b = FutureProvider.autoDispose<List<String>>((ref) async {
final value = ref.watch(a);
// Doing another operation
await Future.delayed(const Duration(seconds: 2));
return value.maybeWhen(
data: (data) => data.map((e) => 'B $e').toList(),
orElse: () => [],
);
});

Future<dynamic> is not a subtype of List<dynamic>

So I am trying to pass a list of String values from firestore table, but I am getting an exception type 'Future<dynamic>' is not a subtype of type 'List<dynamic>'
This is the function
getLectureList(String userId) async {
var collection = FirebaseFirestore.instance.collection('students');
var docSnapshot = await collection.doc(userId).get();
Map<String, dynamic>? data = docSnapshot.data();
List<String> _lectureList =
await data!['attendance']; //This line is kinda giving me trouble
userInfo = FirestoreWrapper()
.getStudentFromData(docId: currentUser(), rawData: data);
return _lectureList;
}
And this is the function where I am getting the exception thrown
#override
void initState() {
lectureList = getLectureList(currentUser()); // Getting an exception here
NearbyConn(context).searchDevices(devices: deviceList);
super.initState();
}
tried using await in the getLectureList() method but still getting the same problem
Why do you await your data? You already got it.
List<String> _lectureList = data!['attendance'];
Please note that I don't know what your data structure looks like, so I cannot tell you if this is correct, I can only tell you that it is more correct than before, because the await did not belong there.
You are getting an exception here lectureList = getLectureList(currentUser()); because the the parameter required by the getLectureList() method is the userId which is a string. I do not know what currentUser() return but I'm assuming it's the userId that you need when calling the getLectureList() method. Based on the error, it looks like currentUser() is an async method that returns a future after some time.
You're not awaiting that future. You shouldn't make the initState() method async so move the code block out of it into a separate method and then call it from initState().
Something like this,
#override
void initState() {
super.initState();
_getData();
}
void _getData() async {
lectureList =
getLectureList(await currentUser());
NearbyConn(context).searchDevices(devices: deviceList);
}
or
#override
void initState() {
super.initState();
_getData();
}
void _getData() async {
String _userID = await currentUser();
lectureList = getLectureList(_userID);
NearbyConn(context).searchDevices(devices: deviceList);
}
Which I recommend so you can see all the parts.
Making your method parameters required named parameters also help you to easily see what is needed to pass to a function/class/.
Eg.
getLectureList({required String userId}){
...
}
Your IDE will alert you on the type of object the function requires and it makes things clearer.
Ultimately, I think typing your classes makes it so much more easier to fetch data from fireStore Typing CollectionReference and DocumentReference
This way you can easily do this,
final moviesRef = FirebaseFirestore.instance.collection('movies').withConverter<Movie>(
fromFirestore: (snapshot, _) => Movie.fromJson(snapshot.data()!),
toFirestore: (movie, _) => movie.toJson(),
);
and get your data this way,
Future<void> main() async {
// Obtain science-fiction movies
List<QueryDocumentSnapshot<Movie>> movies = await moviesRef
.where('genre', isEqualTo: 'Sci-fi')
.get()
.then((snapshot) => snapshot.docs);
// Add a movie
await moviesRef.add(
Movie(
title: 'Star Wars: A New Hope (Episode IV)',
genre: 'Sci-fi'
),
);
// Get a movie with the id 42
Movie movie42 = await moviesRef.doc('42').get().then((snapshot) => snapshot.data()!);
}
Keeps everything dry and tidy.
< The data comes to list format thats why showing the exception of datatype >
List<String> lectureList = await getLectureList(currentUser()); // use
Future<List<String>> getLectureList(String userId) async {
- your code -
}
Instead of
List _lectureList =
await data!['attendance'];
Try this
_lectureList = await data![] As List

How to explicitly return `void`?

One flutter package has this kind of data requesting workflow:
final cancelListening = await request(
query: query,
onResponse: (response) {
streamController.add(response);
cancelListening(); // I need to cancel it here;
},
);
But this way I obviously have the error: cancelListening can't be referenced before it is declared. As request() returns Future<void Function()> I can do this:
void Function() cancelListening = () {};
cancelListening = await request(
...
And I got Omit type annotations for local variables.dart(omit_local_variable_types).
So, I write this way:
var cancelListening = () {}
cancelListening = await request(
...
But now cancelListening is Null Function() and I'm getting A value of type 'void Function()' can't be assigned to a variable of type 'Null Function()'.
So my questions is:
Is there is a way to explicitly return void in dart? Something like () => Void();
Should I simply ignore this linter rule or there is better way to handle this situation?
Thanks.
You might want to setup an intermediary function to call the received callback. VoidCallback should be a type for functions that specifically return void, instead of null.
VoidCallback? cancelListening;
void stopListening() {
cancelListening?.call();
}
void listen() async {
cancelListening = await request(
query: query,
onResponse: (response) {
streamController.add(response);
stopListening();
},
);
}
Optionally, instead of making cancelListening nullable, you could use the late keyword.
late VoidCallback cancelListening;
void stopListening() {
cancelListening(); // No optional operator `?` needed.
}

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.