【发布时间】:2020-04-07 21:06:23
【问题描述】:
我有一个StatefulWidget,其状态会根据加载状态呈现不同的Widget(加载 -> 加载/错误):
// widget
class ListNotesScreen extends StatefulWidget {
static const route = '/listNotes';
static navigateTo(BuildContext context, [bool cleanStack = true]) =>
Navigator.pushNamedAndRemoveUntil(context, route, (_) => !cleanStack);
final String title;
final ListNotesUseCase _useCase;
final VoidCallback _addNoteCallback;
ListNotesScreen(this._useCase, this._addNoteCallback, {Key key, this.title}) : super(key: key);
@override
_ListNotesScreenState createState() => _ListNotesScreenState();
}
// state
class _ListNotesScreenState extends State<ListNotesScreen> {
ListNotesLoadState _state;
Future<ListNotesResponse> _fetchNotes() async {
return widget._useCase.listNotes();
}
@override
initState() {
super.initState();
_loadNotes();
}
_loadNotes() {
setState(() {
_state = ListNotesLoadingState();
});
_fetchNotes().then((response) {
setState(() {
_state = ListNotesLoadedState(response.notes);
});
}).catchError((error) {
setState(() {
_state = ListNotesLoadErrorState(error);
});
});
}
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: Text('Notes list'),
actions: <Widget>[
IconButton(icon: Icon(Icons.add), onPressed: widget._addNoteCallback),
IconButton(icon: Icon(Icons.refresh), onPressed: () => _loadNotes())
],
),
body: _state.getWidget());
}
// loading states
// State:
@sealed
abstract class ListNotesLoadState {
Widget getWidget();
}
// Loading
class ListNotesLoadingState extends ListNotesLoadState {
@override
Widget getWidget() => Center(child: CircularProgressIndicator(value: null));
}
// Loaded
class ListNotesLoadedState extends ListNotesLoadState {
final List<Note> _notes;
ListNotesLoadedState(this._notes);
@override
Widget getWidget() => ListView.builder(
itemBuilder: (_, int index) => NoteItemWidget(this._notes[index]),
itemCount: this._notes.length,
padding: EdgeInsets.all(18.0));
}
这里是小部件的测试:
void main() {
testWidgets('Notes list is shown', (WidgetTester tester) async {
final title1 = 'Title1';
final title2 = 'Title2';
final body1 = 'Body1';
final body2 = 'Body2';
var notes = [
Note('1', title1, body1),
Note('2', title2, body2),
];
final listUseCase = TestListNotesInteractor(notes);
final widget = ListNotesScreen(listUseCase, null, title: 'List notes');
await tester.pumpWidget(widget);
await tester.pumpAndSettle();
expect(find.text('someInvalidString'), findsNothing);
expect(find.text(title1), findsOneWidget);
expect(find.text(title2), findsOneWidget);
expect(find.text(body1), findsOneWidget);
expect(find.text(body2), findsOneWidget);
// TODO: fix the test (tested manually and it works)
});
}
所以小部件测试器应该等到它设置为在initState() 中加载的状态,然后_loadNotes 将其移动到ListNotesLoadedState 和ListNotesLoadedState.getWidget() 以返回带有预期字符串的ListView (NoteItemWidget root 和很少有 Text 带有预期的字符串)。
但是测试失败了。原因是什么(我能够在应用程序中使用测试交互器并直观地看到预期的文本)?如何在测试失败时分析实际的小部件树?
我倾向于认为 WidgetTester 没有等待 Future 完成(尽管它预计会被嘲笑并在幕后同步,请纠正我)。
可以找到project on Github(一定要调用flutter packages pub run build_runner build来生成json反序列化代码)。
【问题讨论】:
标签: unit-testing testing flutter dart widget