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.