使用 Mockito 模拟依赖关系
某些情况下,单元测试可能会依赖需要从线上 Web 服务或数据库中获取数据的类。这样会带来一些不便,原因如下:
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.
-
原本可以通过的测试可能会失败,因为 Web 服务或数据库可能会返回不符合预期的结果。这种情况被称作“flaky test”。
A passing test might start failing if a web service or database returns unexpected results. This is known as a “flaky test.”
-
使用线上 web 服务或数据库来测试很难覆盖全所有可能成功或失败的场景。
It is difficult to test all possible success and failure scenarios by using a live web service or database.
因此,最好不要依赖线上 web 服务或数据库,我们可以把这些依赖“模拟(mock)”出来。模拟(Mocks)允许我们仿造一个线上服务或数据库,并且可以根据条件返回特定结果。
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.
通常来说,可以通过创建类的另一种实现来模拟(mock)这种依赖。类的另一种实现可以手写,也可以借助 Mockito 包,后者简单一些。
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.
本篇教程介绍了 Mockito 包的基本用法,可以参考以下步骤:
This recipe demonstrates the basics of mocking with the Mockito package using the following steps:
使用步骤
Directions
-
添加
mockito
和test
依赖Add the package dependencies.
-
创建一个要测试的函数
Create a function to test.
-
创建一个模拟了
http.Client
的测试文件Create a test file with a mock
http.Client
. -
给每一个条件写一个测试
Write a test for each condition.
-
执行这些测试
Run the tests.
更多信息可以查阅 Mockito package 的官方文档。
For more information, see the Mockito package documentation.
1. 添加 package 依赖
1. Add the package dependencies
为了使用 mockito 包,首先将其和 flutter_test
的依赖一起添加到 pubspec.yaml
文件的 dev_dependencies
部分:
To use the mockito
package, add it to the
pubspec.yaml
file along with the flutter_test
dependency in the
dev_dependencies
section.
本例中还使用了 http
包,需要添加到 dependencies
部分:
This example also uses the http
package,
so define that dependency in the dependencies
section.
dependencies:
http: <newest_version>
dev_dependencies:
flutter_test:
sdk: flutter
mockito: <newest_version>
2. 创建一个要测试的函数
2. Create a function to test
本例中,我们要对 获取网络数据
章节的 fetchPost
函数进行单元测试。为了便于测试,我们需要做两个改动:
In this example, unit test the fetchPost
function from the
Fetch data from the internet recipe.
To test this function, make two changes:
-
给函数提供一个
http.Client
。这样的话我们可以在不同情形下提供相应的http.Client
实例。如果是 Flutter 以及服务端项目,可以提供http.IOClient
。如果是浏览器应用,可以提供http.BrowserClient
。为了测试,我们要提供一个模拟的http.Client
。Provide an
http.Client
to the function. This allows providing the correcthttp.Client
depending on the situation. For Flutter and server-side projects, provide anhttp.IOClient
. For Browser apps, provide anhttp.BrowserClient
. For tests, provide a mockhttp.Client
. -
使用上面提供的
client
来请求网络数据,不要用http.get()
这个静态方法,因为它比较难以模拟。Use the provided
client
to fetch data from the internet, rather than the statichttp.get()
method, which is difficult to mock.
函数经过改动之后:
The function should now look like this:
class Post {
dynamic data;
Post.fromJson(this.data);
}
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(jsonDecode(response.body));
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load post');
}
}
3. 创建一个模拟了 http.Client 的测试文件
http.Client
3. Create a test file with a mock 接下来,创建测试文件,我们需要在文件中创建 MockitoClient
类。遵循 单元测试介绍 章节的建议,我们在根目录下的 test
文件夹中创建一个名字为 fetch_post_test.dart
的文件。
Next, create a test file along with a MockClient
class.
Following the advice in the Introduction to unit testing recipe,
create a file called fetch_post_test.dart
in the root test
folder.
MockClient
类会实现 http.Client
类。如此一来,我们就可以把 MockClient
传给 fetchPost
函数,还可以在每个测试中返回不同的 http 请求结果。
The MockClient
class implements the http.Client
class. This allows
you to pass the MockClient
to the fetchPost
function,
and return different http responses in each test.
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:http/http.dart' as 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. 给每一个条件写一个测试
4. Write a test for each condition
回过头来看,fetchPost()
函数会完成下面两件事中的一件:
The fetchPost()
function does one of two things:
-
如果 http 请求成功,返回
Post
Returns a
Post
if the http call succeeds -
如果 http 请求失败,抛出
Exception
Throws an
Exception
if the http call fails
因此,我们要测试这两种条件。可以使用 MockClient
类为成功的测试返回一个 “OK” 的请求结果,为不成功的测试返回一个错误的请求结果。
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.
我们使用 Mockito 的 when()
函数来达到以上目的:
Test these conditions using the when()
function provided by
Mockito:
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:http/http.dart' as 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() {
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), isA<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. 执行测试
5. Run the tests
现在我们有了一个带测试的 fetchPost()
函数,开始执行测试!
Now that you have a fetchPost()
function with tests in place,
run the tests.
$ dart test/fetch_post_test.dart
你也可以参考 单元测试介绍 章节用自己喜欢的编辑器来执行测试。
You can also run tests inside your favorite editor by following the instructions in the Introduction to unit testing recipe.
总结
Summary
通过本例,我们已经学会了如何用 Mockito 来测试对 web 服务或数据库有依赖的函数或类。这里只是简短地介绍了 Mockito 库以及模拟(mocking)的概念。更多内容请移步至 Mockito package。
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.