Flutter的WidgetTester.pumpAndSettle()是否等待渲染完成?

时间:2019-12-14 20:54:23

标签: unit-testing testing flutter dart widget

我有一个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将其移动到ListNotesLoadedStateListNotesLoadedState.getWidget()并返回{{1} },带有期望的字符串(ListView的根,少部分NoteItemWidget,带有期望的字符串)。

但是测试失败。是什么原因(我能够在应用程序中使用测试交互器并直观地看到期望的文本)?如何在测试失败时分析实际的Widgets树?

我倾向于认为WidgetTester不会等待Text完成(尽管它有望被嘲笑并在后台同步,请纠正我)。

人们可以找到project on Github(确保调用Future来生成json解序列化代码)。

1 个答案:

答案 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中没有提及(实际上是原因吗?),尽管测试代码中有。如果我想念什么,请告诉我。