当点击的小部件启动计时器时,我如何 运行 进行单元测试?

How can I run a unit test when the tapped widget launches a Timer?

我有一个 Widget,单击它会保存 ID 号,覆盖 CircularProgressIndicator 1000 毫秒,然后弹出进度指示器并将用户引导至另一个页面。

这个带有 ProgressIndicator 和 Timer 的位是新的并且破坏了我的单元测试,现在给我以下错误:

The following assertion was thrown running a test:
'package:flutter_test/src/binding.dart': Failed assertion: line 574 pos 12: '() {
      'A Timer is still pending even after the widget tree was disposed.';
      return _fakeAsync.nonPeriodicTimerCount == 0;
    }': is not true.

Either the assertion indicates an error in the framework itself, or we should provide substantially
more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
  https://github.com/flutter/flutter/issues/new

When the exception was thrown, this was the stack:
#2      AutomatedTestWidgetsFlutterBinding._verifyInvariants (package:flutter_test/src/binding.dart:574:12)
#3      TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:415:7)
<asynchronous suspension>
#7      TestWidgetsFlutterBinding._runTest (package:flutter_test/src/binding.dart:392:14)
#8      AutomatedTestWidgetsFlutterBinding.runTest.<anonymous closure> (package:flutter_test/src/binding.dart:549:24)
#14     AutomatedTestWidgetsFlutterBinding.runTest (package:flutter_test/src/binding.dart:547:16)
#15     testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:54:50)
#16     Declarer.test.<anonymous closure>.<anonymous closure> (package:test/src/backend/declarer.dart:131:19)
<asynchronous suspension>
#17     Invoker.waitForOutstandingCallbacks.<anonymous closure>.<anonymous closure> (package:test/src/backend/invoker.dart:200:17)
<asynchronous suspension>
#22     Invoker.waitForOutstandingCallbacks.<anonymous closure> (package:test/src/backend/invoker.dart:197:7)
#26     Invoker.waitForOutstandingCallbacks (package:test/src/backend/invoker.dart:196:5)
#27     Declarer.test.<anonymous closure> (package:test/src/backend/declarer.dart:129:29)
<asynchronous suspension>
#28     Invoker._onRun.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test/src/backend/invoker.dart:322:23)
<asynchronous suspension>
#43     _Timer._runTimers (dart:isolate-patch/timer_impl.dart:385)
#44     _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:414)
#45     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:148)
(elided 31 frames from class _AssertionError, class _FakeAsync, package dart:async, package
dart:async-patch, and package stack_trace)

这是失败的测试的样子:

testWidgets('Tapping item saves its id', (WidgetTester tester) async {
  await launchApp(item, tester);
  await tester.tap(find.byConfig(item));

  expect(sameId(global_state.currentId, item.id), isTrue);
});

有什么方法可以在处理小部件树之前引入延迟吗?我的其他选择是什么?还是我对问题的根本原因有误?

您应该能够在 Quiver 的 FakeAsync 库中的 FakeAsync 中执行此操作: https://www.dartdocs.org/documentation/quiver/0.24.0/quiver.testing.async/FakeAsync-class.html

在Flutter框架本身的测试中有很多这种模式的例子: https://github.com/flutter/flutter/search?utf8=%E2%9C%93&q=FakeAsync

testWidgets 自动引入了一个 FakeAsync 区域,让您可以穿越时空。使用pump提前时间。

不过,您看到的错误是因为您的小部件在处理时没有取消计时器。确保您的 Widget 对象从不分配资源(如计时器),并且您的 State 对象始终在其 dispose 方法中清理它们分配的任何资源。

使用await tester.pumpAndSettle(),默认使用100毫秒延迟,但您可以添加自己的值:await tester.pumpAndSettle(const Duration(seconds:1))

如果您测试使用 Timer 的小部件或幕后的某些动画小部件,它会很有用。

这是一个如何使用它的小例子:

testWidgets('build with $state state', (tester) async {
        when(() => bloc.state).thenReturn(state);
        await blocTester.test(
          tester: tester,
          stateConfig: states[state]!,
        );
        await tester.pumpAndSettle();
      })

对我来说,问题是因为我使用了 visibility_detector 包,它使用了一个计时器。它实际上在他们的 GitHub page:

上声明

Widget tests that use VisibilityDetectors usually should set:

VisibilityDetectorController.instance.updateInterval = Duration.zero

但是我错过了。发帖以防其他人遇到同样的问题。