dart 错误`pumpAndSettle超时`可能是由于riverpod

pb3skfrl  于 2023-04-27  发布在  其他
关注(0)|答案(5)|浏览(125)

我被一个小部件测试卡住了,我需要一些帮助
要重现该行为,请运行下面的***代码示例***

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

任何帮助都是感激的

v09wglhw

v09wglhw1#

我认为这个问题与使用pumpAndSettle和一个无限动画(循环进度指示器)有关。你可以尝试使用pump而不使用settle来自己构建帧。
https://api.flutter.dev/flutter/flutter_test/WidgetTester/pumpAndSettle.html

efzxgjgh

efzxgjgh2#

似乎atm riverpod和pumpAndSettle不工作,作为一个讨厌的快速黑客,你可以尝试这样的东西:

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

nxowjjhe3#

@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);
    });

它会因为accepted answer中描述的原因而中断。嗯,有点。你会得到一种竞争条件,因为我们使用的是异步的future,但上面的代码没有考虑到这一点,所以它执行future widget的代码,但不知道等待它完成创建,所以它存在,一切爆炸。我们需要使整个进程异步。我们通过以下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);

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

5w9g7ksd

5w9g7ksd4#

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);
vnjpjtjt

vnjpjtjt5#

pumpAndSettle()对我来说不起作用,即使是runAsync。我认为原因是来自文档:

  • 如果等待的时间超过了给定的超时时间,则测试将失败(此方法将抛出异常)。特别是,这意味着如果正在进行无限动画(例如,如果存在不确定的进度指示器旋转),则此方法将抛出。*

我创建了一个更高效的@catalyn的答案版本,一旦发现者评估为真,它就停止抽水。

extension PumpUntilFound on WidgetTester {
  Future<void> pumpUntilFound(
    Finder finder, {
    Duration duration = const Duration(milliseconds: 100),
    int tries = 10,
  }) async {
    for (var i = 0; i < tries; i++) {
      await pump(duration);

      final result = finder.precache();

      if (result) {
        finder.evaluate();

        break;
      }
    }
  }
}

使用示例:

final finder = find.byType(AuthScreen);

await tester.pumpUntilFound(finder);

expect(finder, findsOneWidget);

相关问题