Hot questions for Using Mockito in flutter
Question:
I'm trying to make a mock of an httpRequest
in flutter using mockito.
Here I define a global http client:
library utgard.globals; import 'package:http/http.dart' as http; http.Client httpClient = http.Client();
Then I replace in integration testing:
import 'package:flutter_driver/driver_extension.dart'; import 'package:http/http.dart' as http; import 'package:utgard/globals.dart' as globals; import 'package:mockito/mockito.dart'; import 'package:utgard/main.dart' as app; class MockClient extends Mock implements http.Client {} void main() { final MockClient client = MockClient(); globals.httpClient = client; enableFlutterDriverExtension(); app.main(); }
Then I try to use when
of mockito:
test('login with correct password', () async { final client = MockClient(); when(globals.httpClient.post('http://www.google.com')) .thenAnswer((_) async => http.Response('{"title": "Test"}', 200)); await driver.enterText('000000'); await driver.tap(loginContinuePasswordButton); });
But I receive the following error:
Bad state: Mock method was not called within
when()
. Was a real method called?
Answer:
This issue may happen when you implement a method you want to mock instead of letting Mockito do that.
This code below will return Bad state: Mock method was not called within when(). Was a real method called?
:
class MockFirebaseAuth extends Mock implements FirebaseAuth { FirebaseUser _currentUser; MockFirebaseAuth(this._currentUser); // This method causes the issue. Future<FirebaseUser> currentUser() async { return _currentUser; } } final user = MockFirebaseUser(); final mockFirebaseAuth = MockFirebaseAuth(user); // Will throw `Bad state: Mock method was not called within `when()`. Was a real method called?` when(mockFirebaseAuth.currentUser()) .thenAnswer((_) => Future.value(user));
What do you want instead is:
class MockFirebaseAuth extends Mock implements FirebaseAuth {} final user = MockFirebaseUser(); final mockFirebaseAuth = MockFirebaseAuth(); // Will work as expected when(mockFirebaseAuth.currentUser()) .thenAnswer((_) => Future.value(user));
Also this issue happens when you try to call when()
on a non-mock sublass:
class MyClass { String doSomething() { return 'test'; } } final myClassInstance = MyClass(); // Will throw `Bad state: Mock method was not called within `when()`. Was a real method called?` when(myClassInstance.doSomething()) .thenReturn((_) => 'mockedValue');
Question:
I have a method that I would like to mock, however when I am trying to verify calls of this method. I get an error that says:
Used on a non-mockito object
Here is the simplified code:
test('test',() { MockReducer reducer = new MockReducer(); verify(reducer).called(0); }); class MockReducer extends Mock { call(state, action) => state; }
Why can't I do something like this?
Answer:
I think you have three problems here:
- Mockito only works with Classes, not functions (see https://github.com/dart-lang/mockito/issues/62). You have some options: create a test implementation of the function, or for
redux.dart
, you can implement theReducerClass
(which acts as aReducer
function by implementingcall
). - You need to verify methods being called, not the whole Mock class.
- You must use
verifyNever
instead ofverify(X).called(0)
.
Working example:
class MockReducer extends Mock implements ReducerClass {} main() { test('should be able to mock a reducer', () { final reducer = new MockReducer(); verifyNever(reducer.call(any, any)); }); }
Question:
I'm trying to do so using Mockito, this is my test:
import 'package:http/http.dart' as http; import 'package:utgard/globals.dart' as globals; import 'package:flutter_driver/flutter_driver.dart'; import 'package:test/test.dart'; import 'package:mockito/mockito.dart'; class MockClient extends Mock implements http.Client {} void main() { group('Login flow', () { final SerializableFinder loginContinuePasswordButton = find.byValueKey('login_continue_password_button'); FlutterDriver driver; setUpAll(() async { driver = await FlutterDriver.connect(); }); tearDownAll(() async { if (driver != null) { //await driver.close(); } }); test('login with correct password', () async { final client = MockClient(); when(client.post('http://wwww.google.com')) .thenAnswer((_) async => http.Response('{"title": "Test"}', 200)); globals.httpClient = client; await driver.enterText('000000'); await driver.tap(loginContinuePasswordButton); }); }); }
And this is my http request code:
Future<Map<String, dynamic>> post({ RequestType requestType, Map<String, dynamic> body, }) async { final http.Response response = await globals.httpClient.post('http://wwww.google.com'); print(response); final Map<String, dynamic> finalResponse = buildResponse(response); _managerErrors(finalResponse); return finalResponse; }
And here I have the global:
library utgard.globals; import 'package:http/http.dart' as http; http.Client httpClient = http.Client();
However I continue to receive http errors, that indicates to me that the http wasn't replaced by the mock.
Answer:
Instead of
when(client.post('http://wwww.google.com')) .thenAnswer((_) async => http.Response('{"title": "Test"}', 200));
try any
and then assert it later
when( mockHttpClient.send(any), ).thenAnswer((_) async => http.Response('{"title": "Test"}', 200)); // ... final String capt = verify(client.send(captureAny)).captured; expect(capt, 'http://wwww.google.com');
There's a small chance the call param is not exactly what you mock, so go with any
is safer.
Question:
class MockView extends Mock implements ContactListViewContract { @override void onLoadContactsComplete(List<Contact> items) { } @override void onLoadContactsError() {} } void main() { test('ContactListPresenter test', () { Injector.configure(Flavor.MOCK); MockView view = new MockView(); ContactListPresenter presenter = new ContactListPresenter(view); presenter.loadContacts(); verify(view.onLoadContactsComplete).called(1); }); }
I want to make sure when presenter.loadContacts()
is called from the code, then verify view.onLoadContactsComplete
is called also but getting an error:
Used on a non-mockito object
Is there a possibility to do this with Mockito?
Update:
abstract class ContactListViewContract { void onLoadContactsComplete(List<Contact> items); void onLoadContactsError(); }
here the onLoadContactsComplete
method is called
class ContactListPresenter { ContactListViewContract _view; ContactRepository _repository; ContactListPresenter(this._view){ _repository = new Injector().contactRepository; } void loadContacts(){ assert(_view != null); _repository.fetch() .then((contacts) { print(contacts); _view.onLoadContactsComplete(contacts); // here the onLoadContactsComplete method is called }).catchError((onError) { print(onError); _view.onLoadContactsError(); }); } }
Mocked Repository. Fetch mocked data.
class MockContactRepository implements ContactRepository{ Future<List<Contact>> fetch(){ return new Future.value(kContacts); } }
Answer:
when calling verify
method you need call the actual method on the mock
Try
test('ContactListPresenter test', () async { Injector.configure(Flavor.MOCK); MockView view = new MockView(); ContactListPresenter presenter = new ContactListPresenter(view); presenter.loadContacts(); await untilCalled(view.onLoadContactsComplete(typed(any))); //completes when view.onLoadContactsComplete(any) is called verify(view.onLoadContactsComplete(typed(any))).called(1); });
If the method was not called once, the test will fail.
Question:
I'm new to unit testing, especially in Dart/Mockito, so maybe I'm approaching this all wrong. I have an authentication system that has a registerPreLogoutCallback(PreLogoutCallback callback)
method, which adds the given asynchronous callback to a list. When logOut
is called, I use Future.wait
to wait until each of these callbacks has executed, then actually log the user out.
When testing the logOut
method, I'd like to make sure that any registered callbacks have been called. Therefore, I figured I should call registerPreLogoutCallback
a few times in the unit test, and somehow verify that the registered callbacks have been called. Is this possible? Does it violate the purpose of a unit test?
Answer:
If part of logOut
's definition is that it calls the PreLogoutCallback
, then that is part of logOut
's unit test. It's fine to use registerPreLogoutCallback
as part of the test, but you're not testing registerPreLogoutCallback
. Instead, registerPreLogoutCallback
gets its own unit test.
logOut
's unit test would include something like this vaguely Javascript shaped pseudo-code.
// Set the callback to be a closure around `preLogoutCalled`. // registerPreLogoutCalled() has its own unit test, we know it works. var preLogoutCalled = false registerPreLogoutCallback( function() { preLogoutCalled = true } ) // Logout logOut() // Check the callback was called assert(preLogoutCalled)
If logOut
is responsible for passing arguments to the callback, you might put tests in the callback itself to check the callback got the right arguments. Let's say the callback gets the current user.
logged_in_user = ... registerPreLogoutCallback( function(user) { assertEq(user, logged_in_user) } ) logOut()
The best part is it's entirely black-box. Calling the callbacks is part of logOut
's defined behavior. No assumptions need to be made about how logOut()
is implemented.
Question:
I am trying to make fromJson function which parse the json file. But while running test I am getting this error saying.
ERROR: NoSuchMethodError: The method 'toDouble' was called on null. Receiver: null Tried calling: toDouble()
I am not sure what is wrong with this.
My code
weather_model_app_test.dart
group('fromJson', () { test('should return a valid model', () async { final Map<String, dynamic> jsonMap = json.decode(fixture('weather_app.json')); //act final result = WeatherAppModel.fromJson(jsonMap); //assert expect(result, tWeatherAppModel); }); });
weather_app_model.dart
factory WeatherAppModel.fromJson(Map<String, dynamic> json) { return WeatherAppModel( weatherMain: json['weather'][0]['main'], weatherDescription: json['weather'][0]['description'], temp: (json['main']['temp'] as double).toDouble(), minTemp: (json['main']['temp_min'] as double).toDouble(), maxTemp: (json['main']['temp_main'] as double).toDouble(), country: json['sys']['country'], ); }
fixtures/weather_app.dart
{ "coord": { "lon": 78, "lat": 20 }, "weather": [ { "id": 500, "main": "Rain", "description": "light rain", "icon": "10d" } ], "base": "model", "main": { "temp": 301.51, "pressure": 1014, "humidity": 67, "temp_min": 301.51, "temp_max": 301.51, "sea_level": 1014, "grnd_level": 979 }, "wind": { "speed": 3.19, "deg": 77 }, "rain": { "3h": 1.81 }, "clouds": { "all": 45 }, "dt": 1572672029, "sys": { "country": "IN", "sunrise": 1572655775, "sunset": 1572696807 }, "timezone": 19800, "id": 1272596, "name": "Digras", "cod": 200 }
Answer:
Shouldn't
maxTemp: (json['main']['temp_main'] as double).toDouble(),
be [temp_max]
instead of [temp_main]
in your weather_app_model.dart file?