【问题标题】:Flutter's WidgetTester.pumpAndSettle() does not wait for rendering finished?Flutter 的 WidgetTester.pumpAndSettle() 不等待渲染完成?
【发布时间】: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 将其移动到ListNotesLoadedStateListNotesLoadedState.getWidget() 以返回带有预期字符串的ListView (NoteItemWidget root 和很少有 Text 带有预期的字符串)。

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

我倾向于认为 WidgetTester 没有等待 Future 完成(尽管它预计会被嘲笑并在幕后同步,请纠正我)。

可以找到project on Github(一定要调用flutter packages pub run build_runner build来生成json反序列化代码)。

【问题讨论】:

    标签: unit-testing testing flutter dart widget


    【解决方案1】:

    我找到了原因: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 中没有提到它(实际上是这个原因吗?)虽然测试代码有它。如果我错过了什么,请告诉我。

    【讨论】:

    • 这里同样的问题,虽然我使用 MaterialApp 作为根。准确地说,层次结构是 Multprovider -> Consumer -> MaterialApp。之所以使用这个层次结构,是因为我在 Multiprovider 中有一个 I18nProvider,它被消费者监听,当语言发生变化时,它会更新应用程序(在 MaterialApp 小部件内)。
    • 您可以在拨打pumpWidget() 之后尝试拨打await tester.pumpAndSettle();(甚至多次)。在向 MaterialApp 添加本地化后,我的测试也开始失败。我猜这是因为添加本地化会导致 MaterialApp 有一个额外的渲染周期。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-02
    • 2018-04-20
    • 2021-11-21
    • 2018-08-03
    • 2018-05-02
    相关资源
    最近更新 更多