How to use Mockito to verify stream callback - unit-testing

I developing an application an flutter and use clean architecture.
I created a use case return a List from a stream. The stream sends the List from an observer. Above is the code:
abstract class GetAllServicesObserver implements Observer {
void onGetAllSuccess(List<Service> services);
void onGetAllError(Exception error);
}
class GetAllServices extends UseCase<GetAllServicesObserver, NoParams> {
final User _user;
final ServiceRepository _serviceRepository;
StreamSubscription _subscription;
GetAllServices({
#required User user,
#required ServiceRepository serviceRepository,
}) : _user = user,
_serviceRepository = serviceRepository;
#override
action(observer, params) async {
_subscription?.cancel();
final _stream = _serviceRepository.all(_user);
_subscription = _stream.listen((services) {
observer.onGetAllSuccess(services);
}, onError: (e) {
observer.onGetAllError(e);
});
}
}
And I created an unit test to this use case:
test('should to return all services', () {
//setup
when(repository.all(user)).thenAnswer((_) async* {
yield List<Service>();
});
final useCase = GetAllServices(user: user, serviceRepository: repository);
useCase.observer = observer;
//run
useCase();
//verify
verify(observer.onGetAllSuccess(List<Service>()));
});
}
But it's returns the follow message and not pass:
ERROR: No matching calls (actually, no calls at all).
(If you called verify(...).called(0);, please instead use verifyNever(...);.)
Would anyone know what the problem is?

Have you tried untilCalled before verify? e.g.:
await untilCalled(some method that will be called)

Related

Dart Testing with Riverpod StateNotifierProvider and AsyncValue as state

This is my first app with Dart/Flutter/Riverpod, so any advice or comment about the code is welcome.
I'm using Hive as embedded db so the initial value for the provider's state is loaded asynchronously and using an AsyncValue of riverpod to wrapped it.
The following code works but I've got some doubts about the testing approach, so I would like to confirm if I'm using the Riverpod lib as It supposed to be used.
This is my provider with its deps (Preferences is a HiveObject to store app general config data):
final hiveProvider = FutureProvider<HiveInterface>((ref) async {
return await App.setUp();
});
final prefBoxProvider = FutureProvider<Box<Preferences>>((ref) async {
final HiveInterface hive = await ref.read(hiveProvider.future);
return hive.openBox<Preferences>("preferences");
});
class PreferencesNotifier extends StateNotifier<AsyncValue<Preferences>> {
late Box<Preferences> prefBox;
PreferencesNotifier(Future<Box<Preferences>> prefBoxFuture): super(const AsyncValue.loading()) {
prefBoxFuture.then((value) {
prefBox = value;
_loadCurrentPreferences();
});
}
void _loadCurrentPreferences() {
Preferences pref = prefBox.get(0) ?? Preferences();
state = AsyncValue.data(pref);
}
Future<void> save(Preferences prefs) async {
await prefBox.put(0, prefs);
state = AsyncValue.data(prefs);
}
Preferences? get preferences {
return state.when(data: (value) => value,
error: (_, __) => null,
loading: () => null);
}
}
final preferencesProvider = StateNotifierProvider<PreferencesNotifier, AsyncValue<Preferences>>((ref) {
return PreferencesNotifier(ref.read(prefBoxProvider.future));
});
And the following is the test case, I'm mocking the Hive box provider (prefBoxProvider):
class Listener extends Mock {
void call(dynamic previous, dynamic value);
}
Future<Box<Preferences>> prefBoxTesting() async {
final hive = await App.setUp();
Box<Preferences> box = await hive.openBox<Preferences>("testing_preferences");
await box.clear();
return box;
}
void main() {
test('Preferences value changes', () async {
final container = ProviderContainer(overrides: [
prefBoxProvider.overrideWithValue(AsyncValue.data(await prefBoxTesting()))
],);
addTearDown(() {
container.dispose();
Hive.deleteBoxFromDisk("testing_preferences");
});
final listener = Listener();
container.listen<AsyncValue<Preferences>>(
preferencesProvider,
listener,
fireImmediately: true,
);
verify(listener(null, const TypeMatcher<AsyncLoading>())).called(1);
verifyNoMoreInteractions(listener);
// Next line waits until we have a value for preferences attribute
await container.read(preferencesProvider.notifier).stream.first;
verify(listener(const TypeMatcher<AsyncLoading>(), const TypeMatcher<AsyncData>())).called(1);
Preferences preferences = Preferences.from(container.read(preferencesProvider.notifier).preferences!);
preferences.currentListName = 'Lista1';
await container.read(preferencesProvider.notifier).save(preferences);
verify(listener(const TypeMatcher<AsyncData>(), const TypeMatcher<AsyncData>())).called(1);
verifyNoMoreInteractions(listener);
final name = container.read(preferencesProvider.notifier).preferences!.currentListName;
expect(name, equals('Lista1'));
});
}
I've used as reference the official docs about testing Riverpod and the GitHub issue related with AsyncValues
Well, I found some problems to verify that the listener is called with the proper values, I used the TypeMatcher just to verify that the state instance has got the proper type and I check ("manually") the value of the wrapped object's attribute if It's the expected one. Is there a better way to achieve this ?
Finally, I didn't find too many examples with StateNotifier and AsyncValue as state type, Is there a better approach to implement providers that are initialized with deferred data ?
I didn't like too much my original approach so I created my own Matcher to compare wrapped values in AsyncValue instances:
class IsWrappedValueEquals extends Matcher {
final dynamic value;
IsWrappedValueEquals(this.value);
#override
bool matches(covariant AsyncValue actual, Map<dynamic, dynamic> matchState) =>
equals(actual.value).matches(value, matchState);
#override
Description describe(Description description) => description.add('Is wrapped value equals');
}
In the test, the final part is a bit different:
Preferences preferences = Preferences.from(container.read(preferencesProvider.notifier).preferences!);
preferences.currentListName = 'Lista1';
await container.read(preferencesProvider.notifier).save(preferences);
// the following line is the new one
verify(listener(IsWrappedValueEquals(Preferences()), IsWrappedValueEquals(preferences))).called(1);
verifyNoMoreInteractions(listener);
}
I prefer my custom Matcher to the original code, but I feel that there are too many custom code to test something, apparently, common.
If anyone can tell me a better solution for this case, It'd be great.

How can I verify that a request has been made using http_mock_adapter?

I'm using http_mock_adaper to mock Dio HTTP requests. That part works fine, but the thing I am having problems with is verifying that a request has been made using.
One idea I had was to call mockito's verify() with dio.get(any) or adapter.onGet(any, any) as a parameter, but that obviously won't work, since those classes are not mocked using mockito.
Another option I have is to mock the class that calls dio, but that means that I would have to stub every method that is called (Again, since the actual HTTP calls have already been mocked), and I would like to avoid that if possible.
Is there a way to verify that a HTTP call has been made with http_mock_adaper, or is my last option the only / best solution?
Just an example of a base idea. But it can be flexible enough(also for errors and responses).
What if we create our own Interceptor for test purpouse:
class TestHistoryInterceptor extends Interceptor {
final requests = <String>[];
TestHistoryInterceptor();
#override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
requests.add(options.path);
handler.next(options);
}
#override
void onError(DioError err, ErrorInterceptorHandler handler) {
// err.requestOptions.path
handler.next(err);
}
#override
void onResponse(Response response, ResponseInterceptorHandler handler) {
// response.requestOptions.path
handler.next(response);
}
void clear() {
requests.clear();
}
}
and we can use it(changed example from http_mock_adapter repo):
void main() async {
late Dio dio;
late DioAdapter dioAdapter;
//#1
final testHistoryInterceptor = TestHistoryInterceptor();
Response<dynamic> response;
group('Accounts', () {
const baseUrl = 'https://example.com';
const userCredentials = <String, dynamic>{
'email': 'test#example.com',
'password': 'password',
};
setUp(() {
dio = Dio(BaseOptions(baseUrl: baseUrl));
dioAdapter = DioAdapter(dio: dio);
//#2
dio.interceptors.add(testHistoryInterceptor);
});
tearDown(() {
//#3
dio.interceptors.remove(testHistoryInterceptor);
testHistoryInterceptor.clear();
});
test('signs up user', () async {
const route = '/signup';
dioAdapter.onPost(
route,
(server) => server.reply(201, null),
data: userCredentials,
);
// Returns a response with 201 Created success status response code.
response = await dio.post(route, data: userCredentials);
//#4
expect(testHistoryInterceptor.requests, ["/signup"]);
expect(response.statusCode, 201);
});
//...
});
}

How do I stub callbacks of a method?

I am using Firebase Phone Auth in my Flutter project and want to test my auth class. I know how to use when() and .thenAnswer() from Mockito with typical Futures.
I want to test my authentication method, in particular, verificationFailed and verificationCompleted callbacks.
Future<void> getSmsCodeWithFirebase() async {
try {
await _firebaseAuth.verifyPhoneNumber(
phoneNumber: fullPhoneNumber,
timeout: const Duration(seconds: 30),
verificationCompleted: (credential) async {
_firebaseSignIn(credential);
},
verificationFailed: (e) {
errorMessage = 'Error code: ${e.code}';
}
initModelState = DataState.error;
},
codeSent: (String verificationId, int resendToken) {
_firebaseSessionId = verificationId;
initModelState = DataState.idle;
},
codeAutoRetrievalTimeout: (String verificationId) {},
);
} catch (ex) {
errorMessage = 'There was some error';
updateModelState = DataState.error;
}
}
For now I came up with something like this, but I don't understand how to invoke passed callbacks.
test('cant verify phonenumber', () async {
when(mockFirebaseAuth.verifyPhoneNumber(
phoneNumber: any,
codeSent: anyNamed('codeSent'),
verificationCompleted: anyNamed('verificationCompleted'),
verificationFailed: anyNamed('verificationFailed'),
codeAutoRetrievalTimeout: anyNamed('codeAutoRetrievalTimeout')))
.thenAnswer((Invocation invocation) {
// I need to put something here?
});
await authCodeViewModel.getSmsCodeWithFirebase();
expect(authCodeViewModel.initModelState, DataState.error);
});
You're not really asking how to stub callbacks themselves; you're asking how to invoke callbacks for a stubbed method. You'd use captured callback arguments the same as any other captured arguments:
// Sample class with a method that takes a callback.
abstract class Foo {
void f(String Function(int x) callback, int y);
}
#GenerateMocks([Foo])
void main() {
var mockFoo = MockFoo();
mockFoo.f((x) => '$x', 42);
var captured = verify(mockFoo.f(captureAny, any)).captured;
var f = captured[0] as String Function(int);
print(f(88)); // Prints: 88
}
In your case, I think it'd be something like:
test('cant verify phonenumber', () async {
await authCodeViewModel.getSmsCodeWithFirebase();
var captured = verify(mockFirebaseAuth.verifyPhoneNumber(
phoneNumber: any,
codeSent: anyNamed('codeSent'),
verificationCompleted: anyNamed('verificationCompleted'),
verificationFailed: captureNamed('verificationFailed'),
codeAutoRetrievalTimeout: anyNamed('codeAutoRetrievalTimeout')))
.captured;
var verificationFailed = captured[0] as PhoneVerificationFailed;
verificationFailed(FirebaseAuthException());
expect(authCodeViewModel.initModelState, DataState.error);
});
Of course, if you're supplying the callbacks, you don't need to capture them in the first place; you can just invoke them directly yourself.

How can I test / mock Hive (Flutter) open box logic in repo?

Sorry if this seems a dumb question. I'm learning clean architecture as dictated by Rob Martin, and I've having a tiny bit of trouble writing one of my tests.
I wrote a couple functions in a Hive repo. Here's the code
import 'package:hive/hive.dart';
import 'package:movie_browser/features/SearchMovie/domain/entities/movie_detailed_entity.dart';
abstract class HiveMovieSearchRepoAbstract {
Future<void> cacheMovieDetails(MovieDetailed movie);
Future<MovieDetailed> getCachedMovieDetails(String id);
}
// const vars to prevent misspellings
const String MOVIEDETAILSBOX = "MovieDetailedBox";
const String SEARCHBOX = "SearchBox";
class HiveMovieSearchRepo implements HiveMovieSearchRepoAbstract {
Box movieDetailsBox = Hive.box(MOVIEDETAILSBOX) ?? null;
// TODO implement searchbox
// final searchBox = Hive.box(SEARCHBOX);
Future<void> cacheMovieDetails(MovieDetailed movie) async {
/// expects a MovieDetailed to cache. Will cache that movie
movieDetailsBox ?? await _openBox(movieDetailsBox, MOVIEDETAILSBOX);
movieDetailsBox.put('${movie.id}', movie);
}
Future<MovieDetailed> getCachedMovieDetails(String id) async {
/// expects a string id as input
/// returns the MovieDetailed if cached previously
/// returns null otherwise
movieDetailsBox ?? await _openBox(movieDetailsBox, MOVIEDETAILSBOX);
return await movieDetailsBox.get('$id');
}
_openBox(Box box, String type) async {
await Hive.openBox(type);
return Hive.box(type);
}
}
I can't think of how to test this? I want two cases, one where the box is already opened, and one case where it isn't.
Specifically, it's these lines I want to test
movieDetailsBox ?? await _openBox(movieDetailsBox, MOVIEDETAILSBOX);
_openBox(Box box, String type) async {
await Hive.openBox(type);
return Hive.box(type);
}
I thought about mocking the Box object then doing something like....
when(mockHiveMovieSearchRepo.getCachedMovieDetails(some_id)).thenAnswer((_) async => object)
but wouldn't that bypass the code I want tested and always show as positive?
Thanks so much for the help
i don't know if i fully understand your question but you can try something like this
abstract class HiveMovieSearchRepoAbstract {
Future<void> cacheMovieDetails(MovieDetailed movie);
Future<MovieDetailed> getCachedMovieDetails(String id);
}
// const vars to prevent misspellings
const String MOVIEDETAILSBOX = "MovieDetailedBox";
const String SEARCHBOX = "SearchBox";
class HiveMovieSearchRepo implements HiveMovieSearchRepoAbstract {
final HiveInterface hive;
HiveMovieSearchRepo({#required this.hive});
#override
Future<void> cacheMovieDetails(MovieDetailed cacheMovieDetails) async {
/// expects a MovieDetailed to cache. Will cache that movie
try {
final moviedetailbox = await _openBox(MOVIEDETAILSBOX);
moviedetailbox.put('${movie.id}', movie);
} catch (e) {
throw CacheException();
}
}
Future<MovieDetailed> getCachedMovieDetails(String id) async {
/// expects a string id as input
/// returns the MovieDetailed if cached previously
/// returns null otherwise
try {
final moviedetailbox = await _openBox(MOVIEDETAILSBOX);
if (moviedetailbox.containsKey(boxkeyname)) {
return await movieDetailsBox.get('$id');
}
return null;
} catch (e) {
return CacheException();
}
}
Future<Box> _openBox(String type) async {
try {
final box = await hive.openBox(type);
return box;
} catch (e) {
throw CacheException();
}
}
}
And to test it you can do something like this
class MockHiveInterface extends Mock implements HiveInterface {}
class MockHiveBox extends Mock implements Box {}
void main() {
MockHiveInterface mockHiveInterface;
MockHiveBox mockHiveBox;
HiveMovieSearchRepo hiveMovieSearchRepo;
setUp(() {
mockHiveInterface = MockHiveInterface();
mockHiveBox = MockHiveBox();
hiveMovieSearchRepo = HiveMovieSearchRepo(hive: mockHiveInterface);
});
group('cacheMoviedetails', () {
test(
'should cache the movie details',
() async{
//arrange
when(mockHiveInterface.openBox(any)).thenAnswer((_) async => mockHiveBox);
//act
await hiveMovieSearchRepo.cacheMovieDetails(tcacheMovieDetails);
//assert
verify(mockHiveBox.put('${movie.id}', tmovie));
verify(mockHiveInterface.openBox("MovieDetailedBox"));
});
});
group('getLocalCitiesAndCountriesAtPage', () {
test('should when', () async {
//arrange
when(mockHiveInterface.openBox(any))
.thenAnswer((realInvocation) async => mockHiveBox);
when(mockHiveBox.get('$id'))
.thenAnswer((realInvocation) async => tmoviedetails);
//act
final result =
await hiveMovieSearchRepo.getCachedMovieDetails(tId);
//assert
verify(mockHiveInterface.openBox(any));
verify(mockHiveBox.get('page${tpage.toString()}'));
expect(result, tmoviedetails);
});
});
}
You should add some tests also for the CacheExeption().
Hope this help you.
So, I wrote this post 9 months. Stackoverflow just sent me a notification saying it's a popular question, so I'll answer it for anyone else wondering the same thing
Easy way to make this testable is change Box to an arg passed into the class, like so
abstract class ClassName {
final Box movieDetailsBox;
final Box searchBox;
ClassName({
this.moveDetailsBox,
this.searchBox,
});
}
this makes the boxes mockable and testable
You should mock the hive interface and box;

How can I unit test whether the ChangeNotifier's notifyListeners was called in Flutter/Dart?

I'm using the provider package in our app and I want to test my ChangeNotifier class individually to have simple unit tests checking the business logic.
Apart from the values of ChangeNotifier properties, I also want to ensure that in certain cases (where necessary), the notifyListeners has been called, as otherwise, the widgets that rely on up-to-date information from this class would not be updated.
Currently, I'm indirectly testing whether the notifyListeners have been called: I'm using the fact that the ChangeNotifier lets me add a callback using its addListener method. In the callback that I add in our testing suite, I simply increment an integer counter variable and make assertions on that.
Is this the right way to test whether my ChangeNotifier calls its listeners? Is there a more descriptive way of testing this?
Here is the class I'm testing:
import 'package:flutter/foundation.dart';
class ExampleModel extends ChangeNotifier {
int _value = 0;
int get value => _value;
void increment() {
_value++;
notifyListeners();
}
}
and this is how I test it:
import 'package:mobile_app/example_model.dart';
import 'package:test/test.dart';
void main() {
group('$ExampleModel', () {
ExampleModel exampleModel;
int listenerCallCount;
setUp(() {
listenerCallCount = 0;
exampleModel = ExampleModel()
..addListener(() {
listenerCallCount += 1;
});
});
test('increments value and calls listeners', () {
exampleModel.increment();
expect(exampleModel.value, 1);
exampleModel.increment();
expect(listenerCallCount, 2);
});
test('unit tests are independent from each other', () {
exampleModel.increment();
expect(exampleModel.value, 1);
exampleModel.increment();
expect(listenerCallCount, 2);
});
});
}
Your approach seems fine to me but if you want to have a more descriptive way you could also use Mockito to register a mock callback function and test whether and how often the notifier is firing and thus notifying your registered mock instead of incrementing a counter:
import 'package:mobile_app/example_model.dart';
import 'package:test/test.dart';
/// Mocks a callback function on which you can use verify
class MockCallbackFunction extends Mock {
call();
}
void main() {
group('$ExampleModel', () {
late ExampleModel exampleModel;
final notifyListenerCallback = MockCallbackFunction(); // Your callback function mock
setUp(() {
exampleModel = ExampleModel()
..addListener(notifyListenerCallback);
reset(notifyListenerCallback); // resets your mock before each test
});
test('increments value and calls listeners', () {
exampleModel.increment();
expect(exampleModel.value, 1);
exampleModel.increment();
verify(notifyListenerCallback()).called(2); // verify listener were notified twice
});
test('unit tests are independent from each other', () {
exampleModel.increment();
expect(exampleModel.value, 1);
exampleModel.increment();
expect(notifyListenerCallback()).called(2); // verify listener were notified twice. This only works, if you have reset your mocks
});
});
}
Just keep in mind that if you trigger the same mock callback function in multiple tests you have to reset your mock callback function in the setup to reset its counter.
I've ran into the same Issue. It's difficult to test wether notifyListeners was called or not especially for async functions. So I took your Idea with the listenerCallCount and put it to one function you can use.
At first you need a ChangeNotifier:
class Foo extends ChangeNotifier{
int _i = 0;
int get i => _i;
Future<bool> increment2() async{
_i++;
notifyListeners();
_i++;
notifyListeners();
return true;
}
}
Then the function:
Future<R> expectNotifyListenerCalls<T extends ChangeNotifier, R>(
T notifier,
Future<R> Function() testFunction,
Function(T) testValue,
List<dynamic> matcherList) async {
int i = 0;
notifier.addListener(() {
expect(testValue(notifier), matcherList[i]);
i++;
});
final R result = await testFunction();
expect(i, matcherList.length);
return result;
}
Arguments:
The ChangeNotifier you want to test.
The function which should fire notifyListeners (just the reference to the function).
A function to the state you want to test after each notifyListeners.
A List of the expected values of the state you want to test after each notifyListeners (the order is important and the length has to equal the notifyListeners calls).
And this is how to test the ChangeNotifier:
test('should call notifyListeners', () async {
Foo foo = Foo();
expect(foo.i, 0);
bool result = await expectNotifyListenerCalls(
foo,
foo.increment2,
(Foo foo) => foo.i,
<dynamic>[isA<int>(), 2]);
expect(result, true);
});
I have wrap it to the function
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
dynamic checkNotifierCalled(
ChangeNotifier notifier,
Function() action, [
Matcher? matcher,
]) {
var isFired = false;
void setter() {
isFired = true;
notifier.removeListener(setter);
}
notifier.addListener(setter);
final result = action();
// if asynchronous
if (result is Future) {
return result.then((value) {
if (matcher != null) {
expect(value, matcher);
}
return isFired;
});
} else {
if (matcher != null) {
expect(result, matcher);
}
return isFired;
}
}
and call it by:
final isCalled = checkNotifierCalled(counter, () => counter.increment(), equals(2));
expect(isCalled, isTrue);