如何在Flutter中为 API 调用编写测试用例?
在这里,我们将介绍一个构建的应用程序,该应用程序调用 API 并为其编写测试用例,然后再深入研究它,让我们先了解一些基本概念。
软件测试是我们测试代码以确保它在任何给定实例中产生异常结果的过程。
Flutter测试包括:
- 单元测试——测试方法、函数或类
- 小部件测试– 测试单个小部件
- 集成测试——测试大部分或整个应用程序
当我们要测试一个调用 API 的函数时,我们将进行单元测试。让我们创建应用程序。您可以使用flutter create 命令或使用您选择的 IDE 创建应用程序。在应用程序中,我们将使用 Numbers API,它会提供有关数字的随机琐事,然后将测试进行 API 调用的函数。我们假设您对flutter应用程序开发有一定的经验。
首先,我们将在pubspec.yaml 文件中添加 http 依赖项并 pub 获取依赖项。
XML
name: flutter_unit_test
description: A new Flutter project.
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1
environment:
sdk: ">=2.12.0 <3.0.0"
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
http: 0.13.4
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
dev_dependencies:
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^1.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
Dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text(
'GeeksForGeeks',
),
backgroundColor: Colors.green,
),
body: const MyApp(),
),
),
);
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State {
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: getNumberTrivia(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(
color: Colors.green,
),
);
} else if (snapshot.connectionState == ConnectionState.done) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Text(
snapshot.data.toString(),
),
),
);
} else {
return const Center(
child: Text(
'Error Occurred',
),
);
}
});
}
}
Future getNumberTrivia() async {
Uri numberAPIURL = Uri.parse('http://numbersapi.com/random/trivia?json');
final response = await http.get(numberAPIURL);
if (response.statusCode == 200) {
final Map triviaJSON = jsonDecode(response.body);
return triviaJSON['text'];
} else {
return 'Failed to fetch number trivia';
}
}
Dart
Future getNumberTrivia(http.Client http) async {
Uri numberAPIURL = Uri.parse('http://numbersapi.com/random/trivia?json');
final response = await http.get(numberAPIURL);
if (response.statusCode == 200) {
final Map triviaJSON = jsonDecode(response.body);
return triviaJSON['text'];
} else {
return 'Failed to fetch number trivia';
}
}
Dart
class _MyAppState extends State {
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: getNumberTrivia(http.Client()),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(
color: Colors.green,
),
);
} else if (snapshot.connectionState == ConnectionState.done) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Text(
snapshot.data.toString(),
),
),
);
} else {
return const Center(
child: Text(
'Error Occurred',
),
);
}
});
}
}
Dart
import 'dart:convert';
import 'package:flutter_test/flutter_test.dart';
// file which has the getNumberTrivia function
import 'package:flutter_unit_test/main.dart';
import 'package:http/http.dart';
import 'package:http/testing.dart';
void main() {
group('getNumberTrivia', () {
test('returns number trivia string when http response is successful',
() async {
// Mock the API call to return a json response with http status 200 Ok //
final mockHTTPClient = MockClient((request) async {
// Create sample response of the HTTP call //
final response = {
"text":
"22834 is the feet above sea level of the highest mountain
in the Western Hemisphere, Mount Aconcagua in Argentina.",
"number": 22834,
"found": true,
"type": "trivia"
};
return Response(jsonEncode(response), 200);
});
// Check whether getNumberTrivia function returns
// number trivia which will be a String
expect(await getNumberTrivia(mockHTTPClient), isA());
});
test('return error message when http response is unsuccessful', () async {
// Mock the API call to return an
// empty json response with http status 404
final mockHTTPClient = MockClient((request) async {
final response = {};
return Response(jsonEncode(response), 404);
});
expect(await getNumberTrivia(mockHTTPClient),
'Failed to fetch number trivia');
});
});
}
现在让我们创建应用程序。您可以删除 main.js 的所有内容。删除lib文件夹中的dart文件并删除 test 文件夹中的文件。现在我们将在 main.js 中构建应用程序。dart文件。
Dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text(
'GeeksForGeeks',
),
backgroundColor: Colors.green,
),
body: const MyApp(),
),
),
);
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State {
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: getNumberTrivia(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(
color: Colors.green,
),
);
} else if (snapshot.connectionState == ConnectionState.done) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Text(
snapshot.data.toString(),
),
),
);
} else {
return const Center(
child: Text(
'Error Occurred',
),
);
}
});
}
}
Future getNumberTrivia() async {
Uri numberAPIURL = Uri.parse('http://numbersapi.com/random/trivia?json');
final response = await http.get(numberAPIURL);
if (response.statusCode == 200) {
final Map triviaJSON = jsonDecode(response.body);
return triviaJSON['text'];
} else {
return 'Failed to fetch number trivia';
}
}
使用 IDE 中的flutter run 命令或运行按钮运行应用程序。
为了编写测试,我们需要了解要测试的方法。这里我们有一个名为 getNumberTrivia 的函数,它调用一个 API,该 API 在成功时返回数字琐事的 JSON 响应,否则返回错误消息。
现在我们可以编写两个测试用例,首先测试成功的 API 响应是否返回包含数字琐事的文本,其次当 API 调用失败时返回错误消息。
在开始测试之前,我们需要了解我们不应该在测试中发出 HTTP 请求。不推荐。相反,我们必须使用模拟或存根。幸运的是, HTTP 包提供了测试。 dart文件供我们使用。
建议我们在项目的根目录下创建一个名为 test 的文件夹,并在其中编写我们的测试。当我们创建它时,它已经存在于我们的项目中。确保您的开发依赖项中有 flutter_test,并且pubspec.yaml 文件的依赖项部分中有 http。
在我们测试函数之前,我们应该将 http 依赖项作为函数的参数,它将在测试的模拟和存根部分帮助我们。
Dart
Future getNumberTrivia(http.Client http) async {
Uri numberAPIURL = Uri.parse('http://numbersapi.com/random/trivia?json');
final response = await http.get(numberAPIURL);
if (response.statusCode == 200) {
final Map triviaJSON = jsonDecode(response.body);
return triviaJSON['text'];
} else {
return 'Failed to fetch number trivia';
}
}
Dart
class _MyAppState extends State {
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: getNumberTrivia(http.Client()),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(
color: Colors.green,
),
);
} else if (snapshot.connectionState == ConnectionState.done) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Text(
snapshot.data.toString(),
),
),
);
} else {
return const Center(
child: Text(
'Error Occurred',
),
);
}
});
}
}
创建一个dart文件,您可以将其命名为 get_number_trivia_unit_test。 dart 。确保将 _test 放在文件名的末尾,这有助于在使用flutter test 命令时flutter理解测试文件。
flutter测试需要了解的一些函数。这些函数来自flutter_test包。
- 组(描述,正文);您可以使用 group函数对与特定函数相关的测试用例进行分组。您可以在 description 参数中编写测试的描述,主体包含所有测试用例。
- 测试(描述,正文);您可以使用 test函数编写测试用例,您可以在参数中提供描述,主体将包含测试用例本身。
- 期望(实际,匹配器);您可以使用 expect 方法测试输出,方法是将函数的输出作为实际参数提供,将预期输出作为匹配器提供。
您可以在官方文档中找到各种匹配器。
Dart
import 'dart:convert';
import 'package:flutter_test/flutter_test.dart';
// file which has the getNumberTrivia function
import 'package:flutter_unit_test/main.dart';
import 'package:http/http.dart';
import 'package:http/testing.dart';
void main() {
group('getNumberTrivia', () {
test('returns number trivia string when http response is successful',
() async {
// Mock the API call to return a json response with http status 200 Ok //
final mockHTTPClient = MockClient((request) async {
// Create sample response of the HTTP call //
final response = {
"text":
"22834 is the feet above sea level of the highest mountain
in the Western Hemisphere, Mount Aconcagua in Argentina.",
"number": 22834,
"found": true,
"type": "trivia"
};
return Response(jsonEncode(response), 200);
});
// Check whether getNumberTrivia function returns
// number trivia which will be a String
expect(await getNumberTrivia(mockHTTPClient), isA());
});
test('return error message when http response is unsuccessful', () async {
// Mock the API call to return an
// empty json response with http status 404
final mockHTTPClient = MockClient((request) async {
final response = {};
return Response(jsonEncode(response), 404);
});
expect(await getNumberTrivia(mockHTTPClient),
'Failed to fetch number trivia');
});
});
}
为了运行测试在终端中输入以下命令
flutter test
输出: