•  


Mocking | Flutter

Mock dependencies using Mockito

Sometimes, unit tests might 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.
  • A passing test might start failing if a web service or database returns unexpected results. This is known as a "flaky test."
  • It is difficult to test all possible success and failure scenarios by using a live web service or database.

Therefore, 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.

Generally speaking, you can mock dependencies by creating an alternative implementation of a class. Write these alternative implementations by hand or make use of the Mockito package as a shortcut.

This recipe demonstrates the basics of mocking with the Mockito package using the following steps:

  1. Add the package dependencies.
  2. Create a function to test.
  3. Create a test file with a mock http.Client .
  4. Write a test for each condition.
  5. Run the tests.

For more information, see the Mockito package documentation.

1. Add the package dependencies

#

To use the mockito package, add it to the pubspec.yaml file along with the flutter_test dependency in the dev_dependencies section.

This example also uses the http package, so define that dependency in the dependencies section.

mockito: 5.0.0 supports Dart's null safety thanks to code generation. To run the required code generation, add the build_runner dependency in the dev_dependencies section.

To add the dependencies, run flutter pub add :

$ flutter pub add http dev:mockito dev:build_runner

2. Create a function to test

#

In this example, unit test the fetchAlbum function from the Fetch data from the internet recipe. To test this function, make two changes:

  1. Provide an http.Client to the function. This allows providing the correct http.Client depending on the situation. For Flutter and server-side projects, provide an http.IOClient . For Browser apps, provide an http.BrowserClient . For tests, provide a mock http.Client .
  2. Use the provided client to fetch data from the internet, rather than the static http.get() method, which is difficult to mock.

The function should now look like this:

dart
Future
<
Album
> 
fetchAlbum
(http.
Client
 client) 
async
 {

  final
 response = 
await
 client

      .
get
(
Uri
.
parse
(
'https://jsonplaceholder.typicode.com/albums/1'
));


  if
 (response.statusCode == 
200
) {

    // If the server did return a 200 OK response,

    // then parse the JSON.

    return
 Album
.
fromJson
(
jsonDecode
(response.body) 
as
 Map
<
String
, 
dynamic
>);

  } 
else
 {

    // If the server did not return a 200 OK response,

    // then throw an exception.

    throw
 Exception
(
'Failed to load album'
);

  }

}

In your app code, you can provide an http.Client to the fetchAlbum method directly with fetchAlbum(http.Client()) . http.Client() creates a default http.Client .

3. Create a test file with a mock http.Client

#

Next, create a test file.

Following the advice in the Introduction to unit testing recipe, create a file called fetch_album_test.dart in the root test folder.

Add the annotation @GenerateMocks([http.Client]) to the main function to generate a MockClient class with mockito .

The generated MockClient class implements the http.Client class. This allows you to pass the MockClient to the fetchAlbum function, and return different http responses in each test.

The generated mocks will be located in fetch_album_test.mocks.dart . Import this file to use them.

dart
import
 'package:http/http.dart'
 as
 http;

import
 'package:mocking/main.dart'
;

import
 'package:mockito/annotations.dart'
;


// Generate a MockClient using the Mockito package.

// Create new instances of this class in each test.

@GenerateMocks
([http.
Client
])

void
 main
() {

}

Next, generate the mocks running the following command:

$ dart run build_runner build

4. Write a test for each condition

#

The fetchAlbum() function does one of two things:

  1. Returns an Album if the http call succeeds
  2. Throws an Exception if the http call fails

Therefore, you want to test these two conditions. Use the MockClient class to return an "Ok" response for the success test, and an error response for the unsuccessful test. Test these conditions using the when() function provided by Mockito:

dart
import
 'package:flutter_test/flutter_test.dart'
;

import
 'package:http/http.dart'
 as
 http;

import
 'package:mocking/main.dart'
;

import
 'package:mockito/annotations.dart'
;

import
 'package:mockito/mockito.dart'
;


import
 'fetch_album_test.mocks.dart'
;


// Generate a MockClient using the Mockito package.

// Create new instances of this class in each test.

@GenerateMocks
([http.
Client
])

void
 main
() {

  group
(
'fetchAlbum'
, () {

    test
(
'returns an Album if the http call completes successfully'
, () 
async
 {

      final
 client = 
MockClient
();


      // Use Mockito to return a successful response when it calls the

      // provided http.Client.

      when
(client

              .
get
(
Uri
.
parse
(
'https://jsonplaceholder.typicode.com/albums/1'
)))

          .
thenAnswer
((_) 
async
 =>

              http.
Response
(
'{"userId": 1, "id": 2, "title": "mock"}'
, 
200
));


      expect
(
await
 fetchAlbum
(client), 
isA
<
Album
>());

    });


    test
(
'throws an exception if the http call completes with an error'
, () {

      final
 client = 
MockClient
();


      // Use Mockito to return an unsuccessful response when it calls the

      // provided http.Client.

      when
(client

              .
get
(
Uri
.
parse
(
'https://jsonplaceholder.typicode.com/albums/1'
)))

          .
thenAnswer
((_) 
async
 => http.
Response
(
'Not Found'
, 
404
));


      expect
(
fetchAlbum
(client), throwsException);

    });

  });

}

5. Run the tests

#

Now that you have a fetchAlbum() function with tests in place, run the tests.

$ flutter test test/fetch_album_test.dart

You can also run tests inside your favorite editor by following the instructions in the Introduction to unit testing recipe.

Complete example

#
lib/main.dart
#
dart
import
 'dart:async'
;

import
 'dart:convert'
;


import
 'package:flutter/material.dart'
;

import
 'package:http/http.dart'
 as
 http;


Future
<
Album
> 
fetchAlbum
(http.
Client
 client) 
async
 {

  final
 response = 
await
 client

      .
get
(
Uri
.
parse
(
'https://jsonplaceholder.typicode.com/albums/1'
));


  if
 (response.statusCode == 
200
) {

    // If the server did return a 200 OK response,

    // then parse the JSON.

    return
 Album
.
fromJson
(
jsonDecode
(response.body) 
as
 Map
<
String
, 
dynamic
>);

  } 
else
 {

    // If the server did not return a 200 OK response,

    // then throw an exception.

    throw
 Exception
(
'Failed to load album'
);

  }

}


class
 Album
 {

  final
 int
 userId;

  final
 int
 id;

  final
 String
 title;


  const
 Album
({
required
 this
.userId, 
required
 this
.id, 
required
 this
.title});


  factory
 Album
.
fromJson
(
Map
<
String
, 
dynamic
> json) {

    return
 Album
(

      userId: json[
'userId'
] 
as
 int
,

      id: json[
'id'
] 
as
 int
,

      title: json[
'title'
] 
as
 String
,

    );

  }

}


void
 main
() => 
runApp
(
const
 MyApp
());


class
 MyApp
 extends
 StatefulWidget
 {

  const
 MyApp
({
super
.key});


  @override

  State
<
MyApp
> 
createState
() => 
_MyAppState
();

}


class
 _MyAppState
 extends
 State
<
MyApp
> {

  late
 final
 Future
<
Album
> futureAlbum;


  @override

  void
 initState
() {

    super
.
initState
();

    futureAlbum = 
fetchAlbum
(http.
Client
());

  }


  @override

  Widget
 build
(
BuildContext
 context) {

    return
 MaterialApp
(

      title: 
'Fetch Data Example'
,

      theme: 
ThemeData
(

        colorScheme: 
ColorScheme
.
fromSeed
(seedColor: 
Colors
.deepPurple),

      ),

      home: 
Scaffold
(

        appBar: 
AppBar
(

          title: 
const
 Text
(
'Fetch Data Example'
),

        ),

        body: 
Center
(

          child: 
FutureBuilder
<
Album
>(

            future: futureAlbum,

            builder: (context, snapshot) {

              if
 (snapshot.hasData) {

                return
 Text
(snapshot.data!.title);

              } 
else
 if
 (snapshot.hasError) {

                return
 Text
(
'
${
snapshot
.
error
}
'
);

              }


              // By default, show a loading spinner.

              return
 const
 CircularProgressIndicator
();

            },

          ),

        ),

      ),

    );

  }

}
test/fetch_album_test.dart
#
dart
import
 'package:flutter_test/flutter_test.dart'
;

import
 'package:http/http.dart'
 as
 http;

import
 'package:mocking/main.dart'
;

import
 'package:mockito/annotations.dart'
;

import
 'package:mockito/mockito.dart'
;


import
 'fetch_album_test.mocks.dart'
;


// Generate a MockClient using the Mockito package.

// Create new instances of this class in each test.

@GenerateMocks
([http.
Client
])

void
 main
() {

  group
(
'fetchAlbum'
, () {

    test
(
'returns an Album if the http call completes successfully'
, () 
async
 {

      final
 client = 
MockClient
();


      // Use Mockito to return a successful response when it calls the

      // provided http.Client.

      when
(client

              .
get
(
Uri
.
parse
(
'https://jsonplaceholder.typicode.com/albums/1'
)))

          .
thenAnswer
((_) 
async
 =>

              http.
Response
(
'{"userId": 1, "id": 2, "title": "mock"}'
, 
200
));


      expect
(
await
 fetchAlbum
(client), 
isA
<
Album
>());

    });


    test
(
'throws an exception if the http call completes with an error'
, () {

      final
 client = 
MockClient
();


      // Use Mockito to return an unsuccessful response when it calls the

      // provided http.Client.

      when
(client

              .
get
(
Uri
.
parse
(
'https://jsonplaceholder.typicode.com/albums/1'
)))

          .
thenAnswer
((_) 
async
 => http.
Response
(
'Not Found'
, 
404
));


      expect
(
fetchAlbum
(client), throwsException);

    });

  });

}

Summary

#

In this example, you've learned how to use Mockito to test functions or classes that depend on web services or databases. This is only a short introduction to the Mockito library and the concept of mocking. For more information, see the documentation provided by the Mockito package .

- "漢字路" 한글한자자동변환 서비스는 교육부 고전문헌국역지원사업의 지원으로 구축되었습니다.
- "漢字路" 한글한자자동변환 서비스는 전통문화연구회 "울산대학교한국어처리연구실 옥철영(IT융합전공)교수팀"에서 개발한 한글한자자동변환기를 바탕하여 지속적으로 공동 연구 개발하고 있는 서비스입니다.
- 현재 고유명사(인명, 지명등)을 비롯한 여러 변환오류가 있으며 이를 해결하고자 많은 연구 개발을 진행하고자 하고 있습니다. 이를 인지하시고 다른 곳에서 인용시 한자 변환 결과를 한번 더 검토하시고 사용해 주시기 바랍니다.
- 변환오류 및 건의,문의사항은 juntong@juntong.or.kr로 메일로 보내주시면 감사하겠습니다. .
Copyright ⓒ 2020 By '전통문화연구회(傳統文化硏究會)' All Rights reserved.
 한국   대만   중국   일본