如何捕捉来自Flutter小部件测试中Future的错误?

时间:2019-06-14 14:34:39

标签: flutter dart flutter-test

我在对在Future期间抛出异常的小部件进行小部件测试时遇到问题。

重现该问题的代码

这是一个简单的testwidget,它以非常简单的方式重现了问题(感谢Remi Rousselet简化了问题)。

testWidgets('This test should pass but fails', (tester) async {
  final future = Future<void>.error(42);

  await tester.pumpWidget(FutureBuilder(
    future: future,
    builder: (_, snapshot) {
      return Container();
    },
  ));
});

预期结果

我希望测试能够顺利完成。相反,它失败并显示以下错误:

══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The number 42 was thrown running a test.

When the exception was thrown, this was the stack:
#2      main.<anonymous closure> (file:///C:/Projects/projet_65/mobile_app/test/ui/exception_test.dart:79:18)
#5      TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:0:0)
#8      TestWidgetsFlutterBinding._runTest (package:flutter_test/src/binding.dart:577:14)
#9      AutomatedTestWidgetsFlutterBinding.runTest.<anonymous closure> (package:flutter_test/src/binding.dart:993:24)
#15     AutomatedTestWidgetsFlutterBinding.runTest (package:flutter_test/src/binding.dart:990:15)
#16     testWidgets.<anonymous closure> (package:flutter_test/src/widget_tester.dart:106:22)
#17     Declarer.test.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart:168:27)
#20     Declarer.test.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart:0:0)
#21     Invoker.waitForOutstandingCallbacks.<anonymous closure> (package:test_api/src/backend/invoker.dart:250:15)
#27     Invoker._onRun.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/invoker.dart:399:21)
(elided 17 frames from class _FakeAsync, package dart:async, and package dart:async-patch)

The test description was:
  This test should pass but fails
════════════════════════════════════════════════════════════════════════════════════════════════════

我尝试过的事情

我尝试expect错误,就像如果不在Future中那样:

expect(tester.takeException(), equals(42));

但是该语句失败,并出现以下错误

The following TestFailure object was thrown running a test (but after the test had completed):
  Expected: 42
  Actual: null

编辑:

@shb答案对于暴露的情况是正确的。这是一个略微的修改,可以打破它并退回初始错误。这种情况更容易在实际应用中发生(我就是这种情况)

  testWidgets('This test should pass but fails', (tester) async {
      Future future;

      await tester.pumpWidget(
        MaterialApp(
          home: Row(
            children: <Widget>[
              FlatButton(
                child: const Text('GO'),
                onPressed: () { future = Future.error(42);},
              ),
              FutureBuilder(
                future: future,
                builder: (_, snapshot) {
                  return Container();
                },
              ),
            ],
          ),
        ));

      await tester.tap(find.text('GO'));
  });

注意:我自愿忽略了@shb提出的tester.runAsync来匹配最初的问题,因为它在特定情况下不起作用

3 个答案:

答案 0 :(得分:7)

await tester.runAsync(() async { .. }

包裹代码

official documentation runAsync<T>

  

运行执行真正的异步工作的回调。

     

这是针对需要调用异步方法的调用者的   方法产生隔离或操作系统线程的位置,因此不能   通过调用Pump同步执行。

见下文

testWidgets('This test should pass but fails', (tester) async {

    await tester.runAsync(() async {

      final future = Future<void>.error(42);

      await tester.pumpWidget(FutureBuilder(
        future: future,
        builder: (_, snapshot) {
          return Container();
        },
      ));

    });

  });

test passed

编辑:

(提出第二期OP)

在这种情况下,请使用

Future.delayed(Duration.zero, () {
   tester.tap(find.text('GO'));
});

下面的完整代码段

testWidgets('2nd try This test should pass but fails', (tester) async {

Future future;
await tester.runAsync(() async {
  await tester.pumpWidget(
    MaterialApp(
      home: Row(
        children: <Widget>[
          FlatButton(
            child: const Text('GO'),
            onPressed: () {
              future = Future.error(42);
            },
          ),
          FutureBuilder(
            future: future,
            builder: (_, snapshot) {
              return Container();
            },
          ),
        ],
      ),
    ),
  );

  Future.delayed(Duration.zero, () {tester.tap(find.text('GO'));});
});
});

screenshot

编辑2:

后来发现

Future.delayed(Duration.zero, () { tester.tap(find.text('GO')); });

没有被呼叫。

答案 1 :(得分:6)

未来会向听众报告错误。如果Future没有侦听器,它将通知Zone未捕获的错误(src)。这是测试框架从中获取错误的地方。

克服此错误的一种方法是在使Future错误之前等待侦听器。

  testWidgets('This passes', (tester) async {
    final Completer completer = Completer();
    await tester.pumpWidget(FutureBuilder(
      future: completer.future,
      builder: (_, snapshot) {
        return Container();
      },
    ));

    // has subscribers, doesn't inform Zone about uncought error
    completer.completeError(42);
    tester.pumpAndSettle();
  });

答案 2 :(得分:3)

我认为问题是您没有抓住错误,这会使应用程序崩溃。

我尝试捕获错误,测试通过:

代码如下:


testWidgets('This test should pass but fails', (tester) async {
    Future future;

    await tester.pumpWidget(MaterialApp(
      home: Row(
        children: <Widget>[
          FlatButton(
            child: const Text('GO'),
            onPressed: () {
              future = Future.error(42).catchError((error) {});
            },
          ),
          FutureBuilder(
            future: future,
            builder: (_, snapshot) {
              return Container();
            },
          ),
        ],
      ),
    ));

    await tester.tap(find.text('GO'));
  });