在使用 Flutter 进行单元测试期间,对 File 对象的操作从未完成

ota*_*iao 5 dart dart-io flutter flutter-test

我正在为我的 Flutter 应用程序编写单元测试,但在读取 json 文件时遇到一些问题。我有一些 json 文件供我的 api 模拟类使用。每当我调用 File 对象上的方法时,它永远不会结束,但测试会继续执行,并以错误结束,因为它没有从文件对象接收数据。这很奇怪,因为只有当从小部件调用的提供程序调用模拟 api 方法时才会发生这种情况。如果我直接调用模拟 api 方法进行测试,则效果很好。\n我的应用程序的结构如下:WidgetScreen调用->提供商调用->mockApi。

\n\n

Widget LegislacaoScreen 从我的提供者调用方法 carregarLegislacao,该方法又从我的 mockApi 调用方法 getLegislacaoes,该方法从 json 文件读取并将结果返回给提供者,然后通知我的小部件。任何帮助表示赞赏。

\n\n

哦,我正在使用以下命令运行测试:flutter test test/my_test.dart

\n\n

这里有一些代码:

\n\n

LegislacaoScreen:

\n\n
class LegislacaoScreen extends StatefulWidget {\n  @override\n  _LegislacaoScreenState createState() => _LegislacaoScreenState();\n}\n\nclass _LegislacaoScreenState extends State<LegislacaoScreen>\n    with AutomaticKeepAliveClientMixin {\n  @override\n  void initState() {\n    super.initState();\n    Future.microtask(carregarDados);\n  }\n\n  @override\n  bool get wantKeepAlive => true;\n\n  Future<void> carregarDados({bool reset = false}) async {\n    try {\n      final legislacaoProvider =\n          Provider.of<LegislacaoProvider>(context, listen: false);\n\n      await legislacaoProvider.carregarLegislacao(\n        page: legislacaoProvider.currentPage + 1,\n        reset: reset,\n      );\n    } catch (err) {}\n  }\n\n  Widget renderBody(legislacaoProvider) {\n    if (legislacaoProvider.carregandoLegislacao &&\n        legislacaoProvider.legislacoes.isEmpty &&\n        !legislacaoProvider.reseting) {\n      return Center(\n        child: CircularProgressIndicator(),\n      );\n    }\n\n    if (legislacaoProvider.erroCarregandoLegislacao) {\n      return MensagemAcao(\n        mensagem:\n            \'Houve um erro ao carregar os dados.\\nPor favor tente novamente.\',\n        acao: () => carregarDados(reset: true),\n      );\n    }\n\n    return LegislacaoLista(\n      carregandoLegislacao: legislacaoProvider.carregandoLegislacao,\n      legislacoes: legislacaoProvider.legislacoes,\n      reseting: legislacaoProvider.reseting,\n      carregarDados: carregarDados,\n      isFiltroEmpty: legislacaoProvider.isFiltroEmpty,\n    );\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    super.build(context);\n\n    final legislacaoProvider = Provider.of<LegislacaoProvider>(context);\n\n    return Scaffold(\n      appBar: CustomAppBar(\n        title: \'Legisla\xc3\xa7\xc3\xa3o\',\n        actions: <Widget>[\n          IconButton(\n            icon: Icon(\n              Icons.search,\n              color: legislacaoProvider.isFiltroEmpty\n                  ? Colors.white\n                  : CustomColors.verde2,\n            ),\n            onPressed: () {\n              Navigator.of(context)\n                  .pushNamed(PesquisarLegislacaoScreen.routeName);\n            },\n          ),\n        ],\n      ),\n      backgroundColor: Colors.white,\n      body: renderBody(legislacaoProvider),\n    );\n  }\n
Run Code Online (Sandbox Code Playgroud)\n\n

提供商:

\n\n
class LegislacaoProvider with ChangeNotifier {\n  bool _carregandoLegislacao = true;\n  bool _erroCarregandoLegislacao = false;\n  List<Legislacao> legislacoes = [];\n  int currentPage = 0;\n  bool _reseting = false;\n\n  LegislacaoApi _legislacaoApi;\n\n  LegislacaoProvider({LegislacaoApi legislacaoApi})\n      : _legislacaoApi = legislacaoApi ?? LegislacaoApiService();\n\n  bool get carregandoLegislacao {\n    return _carregandoLegislacao;\n  }\n\n  bool get erroCarregandoLegislacao {\n    return _erroCarregandoLegislacao;\n  }\n  bool get reseting {\n    return _reseting;\n  }\n  Future<void> carregarLegislacao({\n    int page = 1,\n    bool reset = false,\n  }) async {\n    try {\n      _carregandoLegislacao = true;\n      _erroCarregandoLegislacao = false;\n      _reseting = reset;\n\n      if (reset) {\n        page = 1;\n      }\n      notifyListeners();\n      legislacoes = await _legislacaoApi.getLegislacoes(\n        page: page,\n        ementa: filtro[\'ementa\'],\n        conteudo: filtro[\'conteudo\'],\n        ano: filtro[\'ano\'],\n        periodoInicialLegislacao: filtro[\'periodoInicialLegislacao\'],\n        periodoFinalLegislacao: filtro[\'periodoFinalLegislacao\'],\n        tipoLegislacao: filtro[\'tipoLegislacao\']?.id,\n        numero: filtro[\'numero\'],\n        autor: filtro[\'autor\'],\n      );\n      _carregandoLegislacao = false;\n      _reseting = false;\n      notifyListeners();\n    } catch (err) {\n      print(err);\n      _erroCarregandoLegislacao = true;\n      _carregandoLegislacao = false;\n      _reseting = false;\n      notifyListeners();\n      throw TaNaLeiException(err.toString());\n    }\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

模拟API:

\n\n
class LegislacaoApiServiceMock extends LegislacaoApi {\n  bool _fail = false;\n\n  LegislacaoApiServiceMock();\n\n  LegislacaoApiServiceMock.fail() : _fail = true;\n\n  @override\n  Future<List<Legislacao>> getLegislacoes({\n    int page = 1,\n    String conteudo,\n    String ementa,\n    String ano,\n    DateTime periodoInicialLegislacao,\n    DateTime periodoFinalLegislacao,\n    int tipoLegislacao,\n    String numero,\n    String autor,\n  }) async {\n    if (_fail) {\n      throw Error();\n    }\n\n\n    final file = File(\'test/data/get_legislacoes.json\');\n    print(\'step 1\');\n    final json = await file.readAsString();\n    print(\'step 2 \'); // <-- NEVER GETS PRINTED\n    return jsonDecode(json)\n        .map<Legislacao>(\n          (elemento) => Legislacao.fromJson(elemento),\n        )\n        .toList();\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

最后,我的测试:

\n\n
final locator = GetIt.instance;\n\nvoid setupLocatorLegislacaoProvider({LegislacaoApi legislacaoApi}) {\n  locator.registerLazySingleton<LegislacaoProvider>(() => LegislacaoProvider(\n        legislacaoApi: legislacaoApi,\n      ));\n}\n\n\nvoid main() async {\n  TestWidgetsFlutterBinding.ensureInitialized();\n  setupLocatorLegislacaoProvider(legislacaoApi: LegislacaoApiServiceMock());\n  group(\'LegislacaoScreen\', () {\n    Widget buildWidget() {\n      return ChangeNotifierProvider.value(\n        value: locator<LegislacaoProvider>();,\n        child: MaterialApp(\n          home: LegislacaoScreen(),\n          routes: routes,\n        ),\n      );\n    }\n\n    testWidgets(\'builds screen\', (WidgetTester tester) async {\n      final child = buildWidget();\n      await tester.pumpWidget(child);\n\n      expect(find.byWidget(child), findsOneWidget);\n    });\n\n\n\n    testWidgets(\'finds list when data is loaded\',\n        (WidgetTester tester) async {\n      await tester.pumpWidget(buildWidget());\n\n      await tester.pump();\n      await tester.pump();\n\n      expect(find.byType(LegislacaoLista), findsOneWidget);\n    });\n  });\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

编辑:

\n\n

这是运行测试时出现的错误消息:

\n\n
\xe2\x9e\x9c  ta_na_lei git:(testes) \xe2\x9c\x97 flutter test test/ui/screens/legislacao/legislacao_screen_test.dart\n00:03 +0: LegislacaoScreen builds screen                                                                                                                     \ndentro de mock 1\n00:04 +1: LegislacaoScreen exibe circular progress indicator quando carregando legislacao e reset true                                                       \ndentro de mock 1\n00:04 +2: LegislacaoScreen exibe lista de legislacao quando legislacoes carregadas                                                                           \ndentro de mock 1\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 one matching node in the widget tree\n  Actual: _WidgetTypeFinder:<zero widgets with type "LegislacaoLista" (ignoring offstage widgets)>\n   Which: means none were found but one was expected\n\nWhen the exception was thrown, this was the stack:\n#4      main.<anonymous closure>.<anonymous closure> (file:///Users/home/Projetos/casacivil/ta_na_lei/test/ui/screens/legislacao/legislacao_screen_test.dart:49:7)\n<asynchronous suspension>\n#5      main.<anonymous closure>.<anonymous closure> (file:///Users/home/Projetos/casacivil/ta_na_lei/test/ui/screens/legislacao/legislacao_screen_test.dart)\n#6      testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:140:29)\n<asynchronous suspension>\n#7      testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart)\n#8      TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:703:19)\n<asynchronous suspension>\n#11     TestWidgetsFlutterBinding._runTest (package:flutter_test/src/binding.dart:683:14)\n#12     AutomatedTestWidgetsFlutterBinding.runTest.<anonymous closure> (package:flutter_test/src/binding.dart:1083:24)\n#18     AutomatedTestWidgetsFlutterBinding.runTest (package:flutter_test/src/binding.dart:1080:15)\n#19     testWidgets.<anonymous closure> (package:flutter_test/src/widget_tester.dart:133:24)\n#20     Declarer.test.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart:172:27)\n<asynchronous suspension>\n#21     Declarer.test.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart)\n#22     Invoker.waitForOutstandingCallbacks.<anonymous closure> (package:test_api/src/backend/invoker.dart:246:15)\n#27     Invoker.waitForOutstandingCallbacks (package:test_api/src/backend/invoker.dart:243:5)\n#28     Declarer.test.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart:170:33)\n#33     Declarer.test.<anonymous closure> (package:test_api/src/backend/declarer.dart:169:13)\n#34     Invoker._onRun.<anonymous closure>.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/invoker.dart:400:30)\n(elided 31 frames from class _FakeAsync, class _RawReceivePortImpl, class _Timer, dart:async, dart:async-patch, and package:stack_trace)\n\nThis was caught by the test expectation on the following line:\n  file:///Users/home/Projetos/casacivil/ta_na_lei/test/ui/screens/legislacao/legislacao_screen_test.dart line 49\nThe test description was:\n  exibe lista de legislacao quando legislacoes carregadas\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\n00:04 +2 -1: LegislacaoScreen exibe lista de legislacao quando legislacoes carregadas [E]                                                                    \n  Test failed. See exception logs above.\n  The test description was: exibe lista de legislacao quando legislacoes carregadas\n\n00:04 +3 -1: Some tests failed.                                                                       \n
Run Code Online (Sandbox Code Playgroud)\n\n

编辑2:

\n\n

在我阅读这篇文章https://medium.com/flutter/event-loop-in-widget-tester-50b3ca5e9fc5后,问题似乎出在异步环境上。我尝试在 runAsync 方法中调用我的测试,但它不起作用,然后我添加了 wait Future.delayed(Duration(seconds: 5)) 并且它起作用了。由于某种原因,读取文件需要很长时间(在其他测试中读取速度非常快)。一件有趣的事情是,具有持续时间的泵不会延迟测试......

\n\n

嗯,现在可以使用此解决方法,但我想知道解决该问题的正确方法。

\n\n
await tester.runAsync(() async {\n        await tester.pumpWidget(buildWidget());\n        await Future.delayed(Duration(seconds: 5));\n\n        await tester.pump(Duration(seconds: 2));\n\n        expect(find.byType(LegislacaoLista), findsOneWidget);\n      });\n
Run Code Online (Sandbox Code Playgroud)\n