Flutter/Dart - Unit Testing with Mockito

While creating unit test for a function, sometimes you may want to mock or stub the result of other functions being called from the function under test. This tutorial shows you how to create a unit test for Flutter application (or any other application using Dart language) using Mockito.

Dependencies

There are two libraries used on this tutorial. The first one is test, which provides a standard way of writing and running tests in Dart. The other is mockito, which is used to mocks function - it's inspired by Mockito on Java. Add the following to dev_dependencies section of pubspec.yml and run get pakcages.

  dev_dependencies:
    mockito: 4.0.0
    test: ^1.5.3

Project Structure

For example, there is a file lib/example.dart containing list of functions we're going to test. The test files should be placed on test directory. To keep the structure clean, it's better to follow the convention. The code and the test file should have the same file path relative to lib and test respectively and add _test to the filename of test file.

  lib
    example.dart
  test
    example_test.dart

The Code to Test

Inside example.dart, there is a simple function formatPostTitle. It has one parameter of type Post. If the post is new, the function will append `[New] ` at the beginning of the title. To determine whether the post is new or not, it makes a call to postHelper.isNewPost.

lib/example.dart

  import 'dart:async';
  
  import 'package:flutter_app/post.dart';
  import 'package:flutter_app/posthelper.dart';
  
  class Example {
    PostHelper postHelper;
  
    Example() {
      this.postHelper = new PostHelper();
    }
  
    Example.withMocks({ this.postHelper });
  
    String formatPostTitle(Post post) {
      bool isNew = postHelper.isNewPost(post);
  
      return isNew ? '[New] ${post.title}' : post.title;
    }
  }

The Unit Test

We are going to use test package for running test. Below is the basic structure of the test file.

  main() {
    group('formatPostTitle', () {
      test('when post is new', () async {

      }

      test('when post is new', () async {

      }
    }
  }

When you run the test file, everything inside main will be executed. The group block is used to define what is being tested, while the test block is used to define each test case.

Since we only test formatPostTitle, we don't need to care whether postHelper.isNewPost works or not. There are two cases. If the post is new, it will return true. Otherwise, it will return false. So, we need to have two test cases and for each case, we need to mock postHelper.isNewPost to return true and false respectively. That means we need to mock PostHelper class.

To create a mock of PostHelper, add the following outside main.

  class MockPostHelper extends Mock implements PostHelper {}

To mock the isNewPost method, here's the example

  when(mockedPostHelper.isNewPost(any))
          .thenReturn(true);

It means if we add the code above in a test block, everytime postHelper.isNewPost, it will always return true.

The last thing we need to handle is checking if the result is in accordance with the expectation. We can use expect - the basic use is passing the expected value as the first argument and the result as the second argument.

  import 'package:flutter_app/post.dart';
  import 'package:flutter_app/posthelper.dart';
  import 'package:mockito/mockito.dart';
  import 'package:test/test.dart';
  
  import 'package:flutter_app/example.dart';
  
  class MockPostHelper extends Mock implements PostHelper {}
  
  main() {
    group('formatPostTitle', () {
      test('when post is new', () async {
        final mockedPostHelper = MockPostHelper();
  
        when(mockedPostHelper.isNewPost(any))
            .thenReturn(true);
  
        Post post = new Post(
          title: 'Flutter/Dart Tutorial',
        );
  
        Example example = new Example.withMocks(postHelper: mockedPostHelper);
        expect('[New] Flutter/Dart Tutorial', await example.formatPostTitle(post));
}); test('when post is not new', () async { final mockedPostHelper = MockPostHelper(); when(mockedPostHelper.isNewPost(any)) .thenReturn(false); Post post = new Post( title: 'Flutter/Dart Tutorial',
); Example example = new Example.withMocks(postHelper: mockedPostHelper); expect('Flutter/Dart Tutorial', await example.formatPostTitle(post));
}); }); }

Mocking Function That Returns Future

How about mocking function that returns Future using Mockito. It's a bit different. For example, there is a new function Future<bool> isPostActive that calls postHelper.fetchPost. fetchPost returns Future<Post> and we want to mock it in the unit test.

  import 'dart:async';
  
  import 'package:flutter_app/post.dart';
  import 'package:flutter_app/posthelper.dart';
  
  class Example {
    PostHelper postHelper;
  
    Example() {
      this.postHelper = new PostHelper();
    }
  
    Example.withMocks({ this.postHelper });
  
    String formatPostTitle(Post post) {
      bool isNew = postHelper.isNewPost(post);
  
      return isNew ? '[New] ${post.title}' : post.title;
    }
  
    Future<bool> isPostActive(int id) async {
      try {
        Post post = await postHelper.fetchPost(id);
        return post.active == true;
      } catch (err) {
        return false;
      }
    }
  }

Instead of using thenReturn, if the function returns Future, we have to use thenAnswer.

  when(mockedPostHelper.fetchPost(1))
    .thenAnswer((_) async => Future.value(
      new Post(
        id: 1,
        userId: 1,
        title: 'Post Title',
        content: 'Post content...',
        active: false,
      )
    )
  );

Another case is throwing error. Sometimes we need to handle if the called function returns error. You can use thenThrow for simulating error.

  when(mockedPostHelper.fetchPost(1))
    .thenThrow(new Error());

Below is the full example of unit test for isPostActive function.

  import 'package:flutter_app/post.dart';
  import 'package:flutter_app/posthelper.dart';
  import 'package:mockito/mockito.dart';
  import 'package:test/test.dart';
  
  import 'package:flutter_app/example.dart';
  
  class MockPostHelper extends Mock implements PostHelper {}
  
  main() {
    group('formatPostTitle', () {
      // ...
    });
  
    group('isPostActive', () {
      test('when post is active', () async {
        final mockedPostHelper = MockPostHelper();
  
        when(mockedPostHelper.fetchPost(1))
          .thenAnswer((_) async => Future.value(
            new Post(
              id: 1,
              userId: 1,
              title: 'Post Title',
              content: 'Post content...',
              active: true,
            )
          ));
  
        Example example = new Example.withMocks(postHelper: mockedPostHelper);
        expect(true, await example.isPostActive(1));
      });
  
      test('when post is inactive', () async {
        final mockedPostHelper = MockPostHelper();
  
        when(mockedPostHelper.fetchPost(1))
          .thenAnswer((_) async => Future.value(
            new Post(
              id: 1,
              userId: 1,
              title: 'Post Title',
              content: 'Post content...',
              active: false,
            )
        ));
  
        Example example = new Example.withMocks(postHelper: mockedPostHelper);
        expect(false, await example.isPostActive(1));
      });

      test('when error', () async {
        final mockedPostHelper = MockPostHelper();
  
        when(mockedPostHelper.fetchPost(1))
          .thenThrow(new Error());
  
        Example example = new Example.withMocks(postHelper: mockedPostHelper);
        expect(false, await example.isPostActive(1));
      });
    });
  }

That's the basic examples of creating unit test with the help of Mockito for mocking dependency to other functions.