DEV Community

Cover image for Using Mockito in Flutter
aseem wangoo
aseem wangoo

Posted on

Using Mockito in Flutter

In case it helped :)
Pass Me A Coffee!!

We will cover briefly:

  1. Setup Mockito
  2. Mocking repository using Mockito
  3. Write unit tests for repository
  4. Write unit tests for the view model
  5. (Optional) Code coverage

Setup Mockito

There are often times when unit tests depend on classes that fetch data from live web services or databases. This is inconvenient for a few reasons:

  • Calling live services or databases slows down test execution.
  • It is difficult to test all possible success and failure scenarios by using a live web service or database.

Mockito
Mockito

You can mock dependencies by creating an alternative implementation of a class. by making use of the Mockito package as a shortcut.

Following is the general idea behind Mockito

Rather than relying on a live web service or database, you can “mock” these dependencies. Mocks allow emulating a live web service or database and return specific results depending on the situation.

We use the mockito package for writing unit tests and for generating the implementation it relies on build_runner. Add the plugins to your pubspec.yaml file

# pubspec.yaml
dependencies:
  mockito: ^5.1.0
dev_dependencies:
  build_runner: ^2.1.7
Enter fullscreen mode Exit fullscreen mode

Detailed description here.

Mocking repository using Mockito

Before starting this, let's see the architecture which we used for our app

The architecture used in the app
The architecture used in the app

For each of the screens, we have the following:

  • Repository: Which handles our business logic
  • ViewModel: Bridge between the UI and the repository
  • View: Actual screen visible to the user
  • Model: Response being cast to models, in order to be consumed in the view.

Repository tests using Mockito

abstract class HomeRepo {
  Future<CarouselModel> fetchData();
}
class HomeRepoImpl extends HomeRepo {
  @override
  Future<CarouselModel> fetchData() async {
    await Future.delayed(const Duration(milliseconds: 1800));
    final resp = await  rootBundle.loadString('assets/data/first_screen.json');
    return carouselModelFromJson(resp);
  }
}
Enter fullscreen mode Exit fullscreen mode

In the above snippet, we are creating a manual delay in order to simulate the network delay, and afterward, we simply fetch our JSON file (present under the assets).

  • For mocking our repository, we first create a file home_repo_test.dartunder test folder as below

Repository testing using Mockito
Repository testing using Mockito

Note: The tests should follow test/folder_name/<name>_test.dart pattern

  • Inside the file, we add the annotation @GenerateMocks([HomeRepoTest]) to the main function to generate a MockHomeRepoTest class with mockito.

By annotating a library element (such as a test file’s main function, or a class) with @GenerateMocks, you are directing Mockito's code generation to write a mock class for each "real" class listed, in a new library.

// IMPORTS OMITTED FOR BREVITY
class HomeRepoTest extends Mock implements HomeRepo {}
@GenerateMocks([HomeRepoTest])
Future<void> main() async {
}
Enter fullscreen mode Exit fullscreen mode

HomeRepoTest: The mock class which will be referred to in the generated class

MockHomeRepoTest: The generated mock class which implements the functions inside the HomeRepo

Next, generate the mocks running the following command:

flutter pub run build_runner build
Enter fullscreen mode Exit fullscreen mode

The generated mocks will be located in home_repo_test.mocks.dart. Import this file to use them inside your test file.

  • We should see something like this if all the above steps were done correctly

Mockito generated class
Mockito generated class

Write unit tests for repository

Let’s write the unit tests for the repository now, with all the necessary setup done in the previous step.

  • We will initialize our mock repository inside the setUpAll
Future<void> main() async {  
  late MockHomeRepoTest homeRepo;
  setUpAll(() {
    homeRepo = MockHomeRepoTest();
  });
  test('test fetchData', () async {
    final model = CarouselModel();
    when(homeRepo.fetchData()).thenAnswer((_) async {
      return model;
    });
    final res = await homeRepo.fetchData();
    expect(res, isA<CarouselModel>());
    expect(res, model);
  });
  test('test fetchData throws Exception', () {
    when(homeRepo.fetchData()).thenAnswer((_) async {
     throw Exception();
    });
    final res = homeRepo.fetchData();
    expect(res, throwsException);
  });
}
Enter fullscreen mode Exit fullscreen mode
  • We have two tests inside the repository: For the first one, we use the when, thenAnswerAPIs provide a stubbing mechanism. Once stubbed, the method will always return the stubbed value regardless of how many times it is called.

when: Creates a stub method response. Mockito will store the fake call and pair the exact arguments given with the response. The response generators from Mockito includethenReturn, thenAnswer, and thenThrow.

thenAnswer: Stores a function which is called when this method stub is called. The function will be called, and the return value will be returned.

We return the CarouselModel from the stub and use the expect to assert that actual matches matcher.

In our case, we expect it to be of type CarouselModel using isA

  • For the second test, we throw an Exception from the stub. We expect the actual matches throwsException which is a matcher for functions that throw Exception.

Write unit tests for the view model

We will first initialize our mock home repository and the view model inside the setUpAll

We pass in the Mock repository MockHomeRepoTestto our view model, as it expects the repository as a required parameter.

Future<void> main() async {
  late MockHomeRepoTest homeRepo;
  late HomeViewModel viewModel;
  setUpAll(() {
    homeRepo = MockHomeRepoTest();
    viewModel = HomeViewModel(repo: homeRepo);
  });
  test('test fetchData', () async {
    final model = CarouselModel();
   when(homeRepo.fetchData()).thenAnswer((_) async {
     return model;
   });
   await viewModel.fetchData();
   expect(viewModel.homeModel, model);
  });
}
Enter fullscreen mode Exit fullscreen mode

We call the homeRepo.fetchData() using the when and return the stub response as the CarouselModel

Next, we call the actual function fetchData() from inside the view model using viewModel.fetchData()

Finally, we expect it to be of type CarouselModel in the matcher, since we are setting the response from the repository to the model.

// HomeViewModel
Future<void> fetchData() async {
  isLoading = true;
  _homeModel = await repo.fetchData();
  isLoading = false;
  notifyListeners();
}
Enter fullscreen mode Exit fullscreen mode

Code coverage

In case you are using Android Studio, you can simply right-click on the test and run tests with Coverage

Code Coverage using Android Studio
Code Coverage using Android Studio

For VSCode, install the following extensions

Execute the following command

flutter test --coverage
Enter fullscreen mode Exit fullscreen mode

This will generate the lcov.infowhich contains the coverage information.

Code Coverage

Now, click on the testing icon on the left side of the VSCode and you can see your code coverage. Good article here

Code coverage of view models
Code coverage of view models

Source code.

In case it helped :)
Pass Me A Coffee!!

Discussion (0)