某些情况下,单元测试可能会依赖需要从线上 Web 服务或数据库中获取数据的类。这样会带来一些不便,原因如下:
访问线上服务或数据库会拖慢测试执行效率。
原本可以通过的测试可能会失败,因为 Web 服务或数据库可能会返回不符合预期的结果。这种情况被称作“flaky test”。
使用线上 web 服务或数据库来测试很难覆盖全所有可能成功或失败的场景。
因此,最好不要依赖线上 web 服务或数据库,我们可以把这些依赖 “模拟(mock)”出来 。模拟(Mocks)允许我们仿造一个线上服务或数据库,并且可以根据条件返回特定结果。
通常来说,可以通过创建类的另一种实现来模拟(mock)这种依赖。类的另一种实现可以手写,也可以借助 Mockito 包,后者简单一些。
本篇教程只介绍了 Mockito 包的基本用法,更多用法请参考 Mockito 包文档。
使用步骤
添加
mockito
和test
依赖创建一个要测试的函数
创建一个模拟了
http.Client
的测试文件给每一个条件写一个测试
执行这些测试
1. 添加 mockito 依赖
为了使用 mockito 包,首先将其和 flutter_test
的依赖一起添加到 pubspec.yaml
文件的 dev_dependencies
部分:
本例中还使用了 http
包,需要添加到 dependencies
部分:
dependencies:
http: <newest_version>
dev_dependencies:
test: <newest_version>
mockito: <newest_version>
2. 创建一个要测试的函数
本例中,我们要对获取网络数据章节的 fetchPost
函数进行单元测试。为了便于测试,我们需要做两个改动:
给函数提供一个
http.Client
。这样的话我们可以在不同情形下提供相应的http.Client
实例。如果是 Flutter 以及服务端项目,可以提供http.IOClient
。如果是浏览器应用,可以提供http.BrowserClient
。为了测试,我们要提供一个模拟的http.Client
。使用上面提供的
client
来请求网络数据,不要用http.get
这个静态方法,因为它比较难以模拟。
函数经过改动之后:
Future<Post> fetchPost(http.Client client) async {
final response =
await client.get('https://jsonplaceholder.typicode.com/posts/1');
if (response.statusCode == 200) {
// If the call to the server was successful, parse the JSON
return Post.fromJson(json.decode(response.body));
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load post');
}
}
3. 创建一个模拟了 http.Client 的测试文件
接下来,创建测试文件,我们需要在文件中创建 MockitoClient
类。遵循单元测试介绍章节的建议,我们在根目录下的 test
文件夹中创建一个名字为 fetch_post_test.dart
的文件。
MockClient
类会实现 http.Client
类。如此一来,我们就可以把 MockClient
传给 fetchPost
函数,还可以在每个测试中返回不同的 http 请求结果。
// Create a MockClient using the Mock class provided by the Mockito package.
// Create new instances of this class in each test.
class MockClient extends Mock implements http.Client {}
main() {
// Tests go here
}
4. 给每一个条件写一个测试
回过头来看,fetchPost
函数会完成下面两件事中的一件:
如果 http 请求成功,返回
Post
如果 http 请求失败,抛出
Exception
因此,我们要测试这两种条件。可以使用 MockClient
类为成功的测试返回一个 “OK” 的请求结果,为不成功的测试返回一个错误的请求结果。
我们使用 Mockito 的 when
函数来达到以上目的:
// Create a MockClient using the Mock class provided by the Mockito package.
// Create new instances of this class in each test.
class MockClient extends Mock implements http.Client {}
main() {
group('fetchPost', () {
test('returns a Post 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('https://jsonplaceholder.typicode.com/posts/1'))
.thenAnswer((_) async => http.Response('{"title": "Test"}', 200));
expect(await fetchPost(client), isInstanceOf<Post>());
});
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('https://jsonplaceholder.typicode.com/posts/1'))
.thenAnswer((_) async => http.Response('Not Found', 404));
expect(fetchPost(client), throwsException);
});
});
}
5. 执行测试
现在我们有了一个带测试的 fetchPost
函数,开始执行测试!
$ dart test/fetch_post_test.dart
你也可以参考单元测试介绍章节用自己喜欢的编辑器来执行测试。
总结
通过本例,我们已经学会了如何用 Mockito 来测试对 web 服务或数据库有依赖的函数或类。这里只是简短地介绍了 Mockito 库以及模拟(mocking)的概念。更多内容请移步至 Mockito package。