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

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.

Related

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

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.
}

How to cast Right side of Either to generic type in flutter

I have an extension to Task (from the dartz package) which looks like this
extension TaskX<T extends Either<Object, U>, U> on Task<T> {
Task<Either<Failure, U>> mapLeftToFailure() {
return this.map(
// Errors are returned as objects
(either) => either.leftMap((obj) {
try {
// So we cast them into a Failure
return obj as Failure;
} catch (e) {
// If it's an unexpected error (not caught by the service)
// We simply rethrow it
throw obj;
}
}),
);
}
}
The goal of this extension is to return a value of type Either<Failure, U>
This was working fine, until I switch my flutter project to null safety.
Now, for some reason, the return type is Either<Failure, dynamic>.
In my code, it looks like so:
await Task(
() => _recipesService.getProduct(),
)
.attempt() // Attempt to run the above code, and catch every exceptions
.mapLeftToFailure() // this returns Task<Either<Failure, dynamic>>
.run()
.then(
(product) => _setProduct(product), // Here I get an error because I expect a type of Either<Failure, Product>
);
My question is, how can I cast the Right side of Either to the correct type?

Calling two methods from a Future Either method, both with Future Either return type

I have these two methods:
Future<Either<Failure, WorkEntity>> getWorkEntity({int id})
and
Future<Either<Failure, WorkEntity>> updateWorkEntity({int id, DateTime executed})
They are both tested and work as expected. I then have this third method that combines the two:
Future<Either<Failure, WorkEntity>> call(Params params) async {
final workEntityEither = await repository.getWorkEntity(id: params.id);
return await workEntityEither.fold((failure) => Left(failure), (workEntity) => repository.updateWorkEntity(id: workEntity.id, executed: DateTime.now()));
}
This method does not work, it always return null. I suspect it is because I do not know what to return in the fold methods. How can this be made to work?
Thank you
Søren
The signature for the fold method is as follows:
fold<B>(B ifLeft(L l), B ifRight(R r)) → B
your ifLeft "Left(failure)" is returning an Either<Failure, WorkEntity> but ifRight "repository.updateWorkEntity(id: workEntity.id, executed: DateTime.now())" is returning a Future.
The easiest solution would be, as described here: How to extract Left or Right easily from Either type in Dart (Dartz)
Future<Either<Failure, WorkEntity>> call(Params params) async {
final workEntityEither = await repository.getWorkEntity(id: params.id);
if (workEntityEither.isRight()) {
// await is not needed here
return repository.updateWorkEntity(id: workEntityEither.getOrElse(null).id, executed: DateTime.now());
}
return Left(workEntityEither);
}
Also this might work (also untested):
return workEntityEither.fold((failure) async => Left(failure), (workEntity) => repository.updateWorkEntity(id: workEntity.id, executed: DateTime.now()));
Since I can not see any benefit in returning an exception I would just throw the exception and catch it with a try/catch block.