如何测试自定义SearchDelegate?

pmi*_*ara 7 dart flutter flutter-test

我正在尝试测试我的习惯SearchDelegate。示例测试将检查在有多少个字符后它开始给出建议。

\n\n

我编写了两个示例测试,它们以某种方式相互影响。它们都是相同的,但是当我一起运行它们时,代码中后面出现的测试总是失败。

\n\n

在调试时,我发现FutureBuilder方法buildSuggestions不会等待searchEngine.search(query)未来完成,但它只发生在第二次测试中。

\n\n

我已经尝试在点击搜索图标后添加一个test.runAsyncwith Future.delayedinside 。另外,我简化了案例以使其更具可读性。

\n\n

您可以在这里找到完整的代码:https://github.com/pmiara/search-delegate-test-fail \也可以在下面查看。

\n\n

申请代码:

\n\n
import \'dart:convert\';\n\nimport \'package:flutter/material.dart\';\nimport \'package:flutter/services.dart\';\n\nclass Entity {\n  final String value;\n\n  Entity.fromJson(Map<String, dynamic> json) : value = json[\'value\'];\n}\n\nclass MySearchDelegate extends SearchDelegate {\n  final MySearchEngine searchEngine;\n\n  MySearchDelegate({@required this.searchEngine});\n\n  @override\n  List<Widget> buildActions(BuildContext context) {\n    return [];\n  }\n\n  @override\n  Widget buildLeading(BuildContext context) {\n    return IconButton(\n      icon: Icon(Icons.arrow_back),\n      onPressed: () {\n        close(context, null);\n      },\n    );\n  }\n\n  @override\n  Widget buildResults(BuildContext context) {\n    return FutureBuilder<List<Entity>>(\n      future: searchEngine.search(query),\n      builder: (BuildContext context, AsyncSnapshot<List<Entity>> snapshot) {\n        if (snapshot.connectionState == ConnectionState.done &&\n            snapshot.hasData) {\n          final entities = snapshot.data;\n          return ListView.builder(\n            itemCount: entities.length,\n            itemBuilder: (context, index) => ListTile(\n              title: Text(entities[index].value),\n              onTap: () => close(context, entities[index]),\n            ),\n          );\n        } else {\n          return Column();\n        }\n      },\n    );\n  }\n\n  @override\n  Widget buildSuggestions(BuildContext context) {\n    return FutureBuilder<List<Entity>>(\n      future: searchEngine.search(query),\n      builder: (BuildContext context, AsyncSnapshot<List<Entity>> snapshot) {\n        if (snapshot.connectionState == ConnectionState.done &&\n            snapshot.hasData) {\n          final entities = snapshot.data;\n          return ListView.builder(\n            itemCount: entities.length,\n            itemBuilder: (context, index) => ListTile(\n              title: Text(entities[index].value),\n              onTap: () {\n                query = entities[index].value;\n                showResults(context);\n              },\n            ),\n          );\n        } else {\n          return Column();\n        }\n      },\n    );\n  }\n}\n\nclass MySearchEngine {\n  Future<List<Entity>> search(String query) async {\n    final jsonEntities =\n        await rootBundle.loadString(\'test_resources/entities.json\');\n    final entities = jsonDecode(jsonEntities)\n        .map<Entity>((json) => Entity.fromJson(json))\n        .toList();\n    return entities;\n  }\n}\n\nclass TestHomePage extends StatelessWidget {\n  final MySearchDelegate delegate;\n\n  const TestHomePage({@required this.delegate});\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      home: Builder(\n        builder: (BuildContext context) {\n          return Scaffold(\n            body: Center(\n              child: IconButton(\n                icon: Icon(Icons.search),\n                onPressed: () async {\n                  showSearch(\n                    context: context,\n                    delegate: delegate,\n                  );\n                },\n              ),\n            ),\n          );\n        },\n      ),\n    );\n  }\n}\n\n/// Run to see what tests should "see"\nvoid main() => runApp(\n      TestHomePage(\n        delegate: MySearchDelegate(\n          searchEngine: MySearchEngine(),\n        ),\n      ),\n    );\n
Run Code Online (Sandbox Code Playgroud)\n\n

测试文件:

\n\n
import \'package:flutter/material.dart\';\nimport \'package:flutter_test/flutter_test.dart\';\nimport \'package:search_delegate_test/search_delegate_problem.dart\';\n\nvoid main() {\n  testWidgets(\'First test\', (WidgetTester tester) async {\n    final delegate = MySearchDelegate(\n      searchEngine: MySearchEngine(),\n    );\n    await tester.pumpWidget(\n      TestHomePage(\n        delegate: delegate,\n      ),\n    );\n    await tester.tap(find.byIcon(Icons.search));\n    await tester.pumpAndSettle();\n    await tester.enterText(find.byType(TextField), \'query\');\n    await tester.pumpAndSettle();\n\n    expect(find.byType(ListTile), findsNWidgets(3));\n  });\n\n  testWidgets(\'Second test\', (WidgetTester tester) async {\n    final delegate = MySearchDelegate(\n      searchEngine: MySearchEngine(),\n    );\n    await tester.pumpWidget(\n      TestHomePage(\n        delegate: delegate,\n      ),\n    );\n    await tester.tap(find.byIcon(Icons.search));\n    await tester.pumpAndSettle();\n    await tester.enterText(find.byType(TextField), \'query\');\n    await tester.pumpAndSettle();\n\n    expect(find.byType(ListTile), findsNWidgets(3));\n  });\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

pubsec.yaml:

\n\n
name: search_delegate_test\ndescription: A new Flutter application.\n\nversion: 1.0.0+1\n\nenvironment:\n  sdk: ">=2.2.2 <3.0.0"\n\ndependencies:\n  flutter:\n    sdk: flutter\n\ndev_dependencies:\n  flutter_test:\n    sdk: flutter\n  test: any\n\nflutter:\n  assets:\n    - test_resources/\n\n  uses-material-design: true\n
Run Code Online (Sandbox Code Playgroud)\n\n

test_resources/entities.json:

\n\n
[\n  {\n    "value": "abc"\n  },\n  {\n    "value": "abc123"\n  },\n  {\n    "value": "123def"\n  }\n]\n
Run Code Online (Sandbox Code Playgroud)\n\n

结果flutter doctor(我正在使用 Android Studio):

\n\n
Doctor summary (to see all details, run flutter doctor -v):\n[\xe2\x9c\x93] Flutter (Channel stable, v1.7.8+hotfix.4, on Linux, locale pl_PL.UTF-8)\n\n[\xe2\x9c\x93] Android toolchain - develop for Android devices (Android SDK version 28.0.3)\n[\xe2\x9c\x93] Android Studio (version 3.4)\n[!] IntelliJ IDEA Ultimate Edition (version 2019.1)\n    \xe2\x9c\x97 Flutter plugin not installed; this adds Flutter specific functionality.\n    \xe2\x9c\x97 Dart plugin not installed; this adds Dart specific functionality.\n[!] IntelliJ IDEA Community Edition (version 2019.1)\n    \xe2\x9c\x97 Flutter plugin not installed; this adds Flutter specific functionality.\n    \xe2\x9c\x97 Dart plugin not installed; this adds Dart specific functionality.\n[\xe2\x9c\x93] Connected device (1 available)\n\n! Doctor found issues in 2 categories.\n
Run Code Online (Sandbox Code Playgroud)\n\n

我得到的错误:

\n\n
\xe2\x95\x90\xe2\x95\x90\xe2\x95\xa1 EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK \xe2\x95\x9e\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\nThe following TestFailure object was thrown running a test:\n  Expected: exactly 3 matching nodes in the widget tree\n  Actual: ?:<zero widgets with type "ListTile" (ignoring offstage widgets)>\n   Which: means none were found but some were expected\n\nWhen the exception was thrown, this was the stack:\n#4      main.<anonymous closure> (file:///path/to/project/search_delegate_test/test/serach_delegate_problem_test.dart:37:5)\n<asynchronous suspension>\n#5      testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:118:25)\n<asynchronous suspension>\n#6      TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:630:19)\n<asynchronous suspension>\n#9      TestWidgetsFlutterBinding._runTest (package:flutter_test/src/binding.dart:613:14)\n#10     AutomatedTestWidgetsFlutterBinding.runTest.<anonymous closure> (package:flutter_test/src/binding.dart:1010:24)\n#16     AutomatedTestWidgetsFlutterBinding.runTest (package:flutter_test/src/binding.dart:1007:15)\n#17     testWidgets.<anonymous closure> (package:flutter_test/src/widget_tester.dart:116:22)\n#18     Declarer.test.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart:168:27)\n<asynchronous suspension>\n#19     Invoker.waitForOutstandingCallbacks.<anonymous closure> (package:test_api/src/backend/invoker.dart:250:15)\n<asynchronous suspension>\n#24     Invoker.waitForOutstandingCallbacks (package:test_api/src/backend/invoker.dart:247:5)\n#25     Declarer.test.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart:166:33)\n#30     Declarer.test.<anonymous closure> (package:test_api/src/backend/declarer.dart:165:13)\n<asynchronous suspension>\n#31     Invoker._onRun.<anonymous closure>.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/invoker.dart:400:25)\n<asynchronous suspension>\n#45     _Timer._runTimers (dart:isolate-patch/timer_impl.dart:382:19)\n#46     _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:416:5)\n#47     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:172:12)\n(elided 28 frames from class _FakeAsync, package dart:async, package dart:async-patch, and package stack_trace)\n\nThis was caught by the test expectation on the following line:\n  file:///path/to/project/search_delegate_test/test/serach_delegate_problem_test.dart line 37\nThe test description was:\n  Second test\n\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\n\nTest failed. See exception logs above.\nThe test description was: Second test\n
Run Code Online (Sandbox Code Playgroud)\n