错误“pumpAndSettle 超时”可能是由于 riverpod

error `pumpAndSettle timed out` MAYBE due to riverpod

我无法进行小部件测试,需要一些帮助
要重现该行为,请 运行 下面的 代码示例

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'home_page.dart';

void main() => runApp(
      const ProviderScope(
        child: MaterialApp(
          home: Material(
            child: MyHomePage(),
          ),
        ),
      ),
    );
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

extension RoundX on double {
  double roundToPrecision(int n) {
    final f = pow(10, n);
    return (this * f).round() / f;
  }
}

final tasksPod = Provider<List<Future<void> Function()>>(
  (ref) => [
    for (var i = 0; i < 10; ++i)
      () async {
        await Future.delayed(kThemeAnimationDuration);
      }
  ],
);

final progressPod = Provider.autoDispose<ValueNotifier<double>>((ref) {
  final notifier = ValueNotifier<double>(0);
  ref.onDispose(notifier.dispose);
  return notifier;
});

class MyHomePage extends HookWidget {
  const MyHomePage() : super(key: const ValueKey('MyHomePage'));

  @override
  Widget build(BuildContext context) {
    final progress = useProvider(progressPod);
    final tasks = useProvider(tasksPod);
    useMemoized(() async {
      final steps = tasks.length;
      if (steps < 1) {
        progress.value = 1;
      } else {
        for (final task in tasks) {
          final current = progress.value;
          if (current >= 1) {
            break;
          }
          await task();
          final value = (current + 1 / steps).roundToPrecision(1);
          print('$value');
          progress.value = value;
        }
      }
    });
    return Center(
      child: ValueListenableBuilder<double>(
        valueListenable: progress,
        child: const FlutterLogo(),
        builder: (context, value, child) =>
            value < 1 ? const CircularProgressIndicator() : child!,
      ),
    );
  }
}

运行应用程序一切正常

✓  Built build/app/outputs/flutter-apk/app-debug.apk.
Installing build/app/outputs/flutter-apk/app.apk...                 4.7s
Syncing files to device Pixel 3a...                                 93ms

Flutter run key commands.
r Hot reload. 
R Hot restart.
h Repeat this help message.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).

 Running with sound null safety 

An Observatory debugger and profiler on Pixel 3a is available at: http://127.0.0.1:36517/50vVndYZ3l4=/
I/flutter (19990): 0.1
I/flutter (19990): 0.2
I/flutter (19990): 0.3
I/flutter (19990): 0.4
I/flutter (19990): 0.5
I/flutter (19990): 0.6
I/flutter (19990): 0.7
The Flutter DevTools debugger and profiler on Pixel 3a is available at: http://127.0.0.1:9101?uri=http%3A%2F%2F127.0.0.1%3A36517%2F50vVndYZ3l4%3D%2F
I/flutter (19990): 0.8
I/flutter (19990): 0.9
I/flutter (19990): 1.0
Application finished.

但未通过此测试

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:timeout_issue/home_page.dart';

void main() {
  testWidgets(
      'WHEN tasks are not completed'
      'THEN shows `CircularProgressIndicator`', (tester) async {
    TestWidgetsFlutterBinding.ensureInitialized();

    await tester.runAsync(() async {
      await tester.pumpWidget(
        ProviderScope(
          child: const MaterialApp(
            home: Material(
              child: MyHomePage(),
            ),
          ),
        ),
      );

      await tester.pumpAndSettle(kThemeAnimationDuration);

      expect(
        find.byType(CircularProgressIndicator),
        findsOneWidget,
        reason: 'CircularProgressIndicator should be shown',
      );
    });
  });
}

使用此输出

00:05 +0: WHEN tasks are not completedTHEN shows `CircularProgressIndicator`                                                                                                                                
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following assertion was thrown while running async test code:
pumpAndSettle timed out

When the exception was thrown, this was the stack:
#0      WidgetTester.pumpAndSettle.<anonymous closure> (package:flutter_test/src/widget_tester.dart:651:11)
<asynchronous suspension>
<asynchronous suspension>
(elided one frame from package:stack_trace)
...
════════════════════════════════════════════════════════════════════════════════════════════════════
00:05 +0 -1: WHEN tasks are not completedTHEN shows `CircularProgressIndicator` [E]                                                                                                                         
  Test failed. See exception logs above.
  The test description was: WHEN tasks are not completedTHEN shows `CircularProgressIndicator`
  
00:05 +0 -1: Some tests failed.                

环境是

Flutter version 2.2.0-11.0.pre.176

environment:
  sdk: ">=2.12.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  hooks_riverpod: ^0.14.0
  flutter_hooks: ^0.16.0

感谢任何帮助

我会说问题与使用 pumpAndSettle 和无限动画(圆形进度指示器)有关。 您可以尝试使用泵来自己搭建框架。

https://api.flutter.dev/flutter/flutter_test/WidgetTester/pumpAndSettle.html

atm riverpod 和 pumpAndSettle 似乎无法正常工作,作为一个讨厌的快速黑客,您可以尝试这样的事情:

for (int i = 0; i < 5; i++) {
  // because pumpAndSettle doesn't work with riverpod
  await tester.pump(Duration(seconds: 1));
}

看来 runAsync 解决了这个问题

await tester.runAsync(() => tester.pumpWidget(
    ProviderScope(child: MyApp()), const Duration(milliseconds: 100)));
final indicator = const CircularProgressIndicator();

await tester.pumpWidget(indicator);

expect(find.byWidget(indicator), findsOneWidget);

@zuldyc 是正确的。通过 运行 异步步骤,它为 Timer 提供了在继续之前成功完成所需的内容。我现在有了一个工作示例,希望它能让事情变得更清楚。

损坏的代码

testWidgets('Testing Login Button Success - New User', (tester) async {
      final amplifyAuthMock = MockAmplifyAuth();
      final dbInterfaceMock = MockDatabaseInterface();

      when(amplifyAuthMock.login('testNew@test.com', 'password!'))
          .thenAnswer((result) async => true);
      when(dbInterfaceMock.startStopDBSync())
          .thenAnswer((realInvocation) async => true);
      when(dbInterfaceMock.restartDBSync())
          .thenAnswer((realInvocation) async => true);

      // CREATING FORM TO TEST
      await tester
          .pumpWidget(createLoginForm(amplifyAuthMock, dbInterfaceMock));
      await inputDummyLoginText(tester, email: 'testNew@test.com');
      
      // PRESSING LOGIN BUTTON AND SHOULD GO TO HOME PAGE
      await tester.tap(find.byType(SkillTreeElevatedButton));

      // BREAKS HERE ON PUMP AND SETTLE******
      await tester.pumpAndSettle(const Duration(seconds: 1));
      expect(find.byType(CircularProgressIndicator), findsOneWidget);
    });

由于接受的答案中描述的原因,它中断了。好吧,有点。你会得到一种竞争条件,因为我们使用的是异步的未来,但上面的代码没有考虑到这一点,所以它执行未来小部件的代码但不知道等待它完成创建,所以它存在并且一切都爆炸了。我们需要使整个过程异步。我们通过遵循 Zuldyc 的回答来做到这一点。通过将我的代码更改为以下代码,它可以正常工作

// THE ABOVE CODE HAS NOT CHANGED, NEW CODE STARTS HERE
await tester
          .runAsync(() => tester.tap(find.byType(SkillTreeElevatedButton)));
      await tester.pump(const Duration(seconds: 1));
      expect(find.byType(CircularProgressIndicator), findsOneWidget);
    });

明确变化如下

//BEFORE
await tester.tap(find.byType(SkillTreeElevatedButton));
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.byType(CircularProgressIndicator), findsOneWidget);
//AFTER
await tester.runAsync(() => tester.tap(find.byType(SkillTreeElevatedButton)));
await tester.pump(const Duration(seconds: 1));
expect(find.byType(CircularProgressIndicator), findsOneWidget);

我的点击操作触发了新屏幕和加载指示器,所以我需要使该操作异步以便完成。