我有一个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()
并返回{{1} },带有期望的字符串(ListView
的根,少部分NoteItemWidget
,带有期望的字符串)。
但是测试失败。是什么原因(我能够在应用程序中使用测试交互器并直观地看到期望的文本)?如何在测试失败时分析实际的Widgets树?
我倾向于认为WidgetTester不会等待Text
完成(尽管它有望被嘲笑并在后台同步,请纠正我)。
人们可以找到project on Github(确保调用Future
来生成json解序列化代码)。
答案 0 :(得分:0)
我已经找到原因:MaterialApp
(或可能是任何应用程序)应该是小部件树的根!
final widget = MaterialApp(home: ListNotesScreen(interactor, null)); // succeeds
代替:
final widget = ListNotesScreen(interactor, null); // fails
我还删除了未使用的title
属性,因此测试代码与我最初使用的形式有所不同:
final widget = ListNotesScreen(listUseCase, null, title: 'List notes');
the docs中没有提及(实际上是原因吗?),尽管测试代码中有。如果我想念什么,请告诉我。