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:

  1. 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 the ReducerClass (which acts as a Reducer function by implementing call).
  2. You need to verify methods being called, not the whole Mock class.
  3. You must use verifyNever instead of verify(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?