많은 위젯은 정보를 표시할 뿐만 아니라 사용자 상호 작용에 응답한다. 여기에는 누를 수 있는 버튼과 텍스트 입력을 위한 TextField이 포함된다.
이러한 상호 작용을 테스트하려면 테스트 환경에서 시뮬레이션하는 방법이 필요하다. 이를 위해 WidgetTester 라이브러리를 사용하라.
WidgetTester는 텍스트 입력, 탭, 드래그 방법을 제공한다.
이 예에서는 세 가지 기능을 테스트하는 기본 todo 앱을 만든다.
class TodoList extends StatefulWidget {
const TodoList({super.key});
State<TodoList> createState() => _TodoListState();
}
class _TodoListState extends State<TodoList> {
static const _appTitle = 'Todo List';
final todos = <String>[];
final controller = TextEditingController();
Widget build(BuildContext context) {
return MaterialApp(
title: _appTitle,
home: Scaffold(
appBar: AppBar(
title: const Text(_appTitle),
),
body: Column(
children: [
TextField(
controller: controller,
),
Expanded(
child: ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];
return Dismissible(
key: Key('$todo$index'),
onDismissed: (direction) => todos.removeAt(index),
background: Container(color: Colors.red),
child: ListTile(title: Text(todo)),
);
},
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
todos.add(controller.text);
controller.clear();
});
},
child: const Icon(Icons.add),
),
),
);
}
}
이제 todo 앱이 있으니 테스트 작성을 시작한다. TextField에 텍스트를 입력하여 시작한다.
다음과 같이 이 작업을 수행한다.
1. 테스트 환경에서 위젯을 빌드한다.
2. WidgetTester에서 enterText() 함수를 사용한다.
testWidgets('Add and remove a todo', (tester) async {
// Build the widget
await tester.pumpWidget(const TodoList());
// Enter 'hi' into the TextField.
await tester.enterText(find.byType(TextField), 'hi');
});
참고: 이 레시피는 이전 위젯 테스트 레시피를 기반으로 한다. 위젯 테스트의 핵심 개념을 알아보려면 다음 레시피를 참조하라.
TextField에 텍스트를 입력한 후 FloatingActionButton을 탭하면 항목이 목록에 추가되는지 확인하라.
여기에는 세 단계가 포함된다.
1. tap() 함수를 사용하여 추가 버튼을 누른다.
2. pump() 함수를 사용하여 상태가 변경된 후 위젯을 다시 빌드한다.
3. 목록 항목이 화면에 나타나는지 확인한다.
testWidgets('Add and remove a todo', (tester) async {
// Enter text code...
// Tap the add button.
await tester.tap(find.byType(FloatingActionButton));
// Rebuild the widget after the state has changed.
await tester.pump();
// Expect to find the item on screen.
expect(find.text('hi'), findsOneWidget);
});
마지막으로 todo 항목에 대해 스와이프하여 닫기 작업을 수행하면 목록에서 해당 항목이 제거되는지 확인한다. 여기에는 세 단계가 포함된다.
1. drag() 함수를 사용하여 스와이프하여 닫기 작업을 수행한다.
2. 닫기 애니메이션이 완료될 때까지 위젯 트리를 계속해서 다시 빌드하는 pumpAndSettle() 함수를 사용한다.
3. 항목이 더 이상 화면에 나타나지 않는지 확인한다.
testWidgets('Add and remove a todo', (tester) async {
// Enter text and add the item...
// Swipe the item to dismiss it.
await tester.drag(find.byType(Dismissible), const Offset(500, 0));
// Build the widget until the dismiss animation ends.
await tester.pumpAndSettle();
// Ensure that the item is no longer on screen.
expect(find.text('hi'), findsNothing);
});
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Add and remove a todo', (tester) async {
// Build the widget.
await tester.pumpWidget(const TodoList());
// Enter 'hi' into the TextField.
await tester.enterText(find.byType(TextField), 'hi');
// Tap the add button.
await tester.tap(find.byType(FloatingActionButton));
// Rebuild the widget with the new item.
await tester.pump();
// Expect to find the item on screen.
expect(find.text('hi'), findsOneWidget);
// Swipe the item to dismiss it.
await tester.drag(find.byType(Dismissible), const Offset(500, 0));
// Build the widget until the dismiss animation ends.
await tester.pumpAndSettle();
// Ensure that the item is no longer on screen.
expect(find.text('hi'), findsNothing);
});
}
class TodoList extends StatefulWidget {
const TodoList({super.key});
State<TodoList> createState() => _TodoListState();
}
class _TodoListState extends State<TodoList> {
static const _appTitle = 'Todo List';
final todos = <String>[];
final controller = TextEditingController();
Widget build(BuildContext context) {
return MaterialApp(
title: _appTitle,
home: Scaffold(
appBar: AppBar(
title: const Text(_appTitle),
),
body: Column(
children: [
TextField(
controller: controller,
),
Expanded(
child: ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];
return Dismissible(
key: Key('$todo$index'),
onDismissed: (direction) => todos.removeAt(index),
background: Container(color: Colors.red),
child: ListTile(title: Text(todo)),
);
},
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
todos.add(controller.text);
controller.clear();
});
},
child: const Icon(Icons.add),
),
),
);
}
}