You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter集成测试组中第二个testWidgets调用失败,单独运行正常

问题分析与解决方案

场景

我有一个简单的Flutter应用,包含3个页面:LoginPage、CheckoutPage和DashboardPage。LoginPage有两个按钮:一个用于跳转至CheckoutPage(键为ValueKey('Subscribe')),另一个用于跳转至DashboardPage(键为ValueKey('Login'))。

代码

我的integration_test/main_test.dart代码如下:

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  group('end to end test', () {
    // setUp(() async {
    //   app.main();
    // });
    testWidgets(
        'Given textfields for Email and Password, When correct values are entered, Then user is taken to their DashboardPage',
        (tester) async {
      // Arrange
      app.main();

      await tester.pumpAndSettle();
      await Future.delayed(const Duration(seconds: 2));

      // Act
      Finder emailTextField = find.byKey(const ValueKey('EmailAddress'));
      Finder passwordTextField = find.byKey(const ValueKey('Password'));
      Finder loginButton = find.byKey(const ValueKey('Login'));
      Finder dashboardPage = find.byType(DashboardPage);

      // Assert
      expect(emailTextField, findsOneWidget);
      expect(passwordTextField, findsOneWidget);
      expect(loginButton, findsOneWidget);

      await tester.enterText(emailTextField, 'ayan@qolarisdata.com');
      await Future.delayed(const Duration(seconds: 2));

      await tester.enterText(passwordTextField, 'Test@123');
      await Future.delayed(const Duration(seconds: 4));

      await tester.tap(loginButton);
      await tester.pumpAndSettle();

      expect(dashboardPage, findsOneWidget);
      await Future.delayed(const Duration(seconds: 5));
    });
    testWidgets(
        'Given a Subscribe button, When the button is tapped on, Then the user is taken to Checkout screen',
        (tester) async {
      app.main();
      // await tester.pumpWidget(const app.MainApp());

      await tester.pumpAndSettle();
      await Future.delayed(const Duration(seconds: 2));

      expect(find.byKey(const ValueKey('Subscribe')), findsOneWidget);
      await tester.tap(find.byKey(const ValueKey('Subscribe')));
      await tester.pumpAndSettle();

      expect(find.byType(CheckoutPage), findsOneWidget);
      await Future.delayed(const Duration(seconds: 5));
    });
  });
}

问题

当我以组为单位运行测试时,第一个测试运行成功,第二个测试失败,报错:

The following TestFailure was thrown running a test:
Expected: exactly one matching candidate
Actual: _KeyWidgetFinder:<Found 0 widgets with key [<'Subscribe'>]: []>

但单独运行第二个测试时,它可以正常通过。我猜测在测试组中,第一个测试完成后应用停留在DashboardPage,第二个测试启动时,应用并未重新从LoginPage开始。

我尝试注释掉app.main(),替换为await tester.pumpWidget(const app.MainApp());或:

await tester.pumpWidget(
      MaterialApp.router(
        routerConfig: GoRouteInformation.router,
      ),
    );

但仍出现相同错误。


核心原因

测试组内的多个测试会共享同一个应用状态:第一个测试执行完成后,应用已经导航到DashboardPage,第二个测试启动时没有重置应用的路由和页面状态,导致找不到LoginPage上的Subscribe按钮。

另外,直接调用app.main()或者重复pumpWidget未必能正确清空路由栈,因为Flutter测试环境中,路由状态不会自动在测试间重置。

修复方案

1. 用setUp统一初始化应用

在测试组内添加setUp方法,确保每个测试开始前都重新初始化应用并等待页面加载完成,彻底重置状态:

setUp(() async {
  await tester.pumpWidget(const app.MainApp());
  await tester.pumpAndSettle();
});

2. 移除不可靠的Future.delayed

集成测试中尽量用tester.pumpAndSettle()代替固定延迟,它会自动等待所有动画、导航等异步操作完成,测试更稳定且耗时更短。

3. 针对路由库的额外处理(如果用GoRouter等)

如果项目用了GoRouter这类路由管理库,需要在setUp中主动重置路由到初始页面:

setUp(() async {
  await tester.pumpWidget(const app.MainApp());
  // 重置路由到登录页
  app.router.go('/login');
  await tester.pumpAndSettle();
});

修改后的完整代码

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  group('end to end test', () {
    setUp(() async {
      // 每个测试前重置应用状态
      await tester.pumpWidget(const app.MainApp());
      await tester.pumpAndSettle();
    });

    testWidgets(
        '输入正确邮箱密码后跳转到DashboardPage',
        (tester) async {
      // 定位元素
      final emailTextField = find.byKey(const ValueKey('EmailAddress'));
      final passwordTextField = find.byKey(const ValueKey('Password'));
      final loginButton = find.byKey(const ValueKey('Login'));
      final dashboardPage = find.byType(DashboardPage);

      // 断言初始页面元素存在
      expect(emailTextField, findsOneWidget);
      expect(passwordTextField, findsOneWidget);
      expect(loginButton, findsOneWidget);

      // 执行操作
      await tester.enterText(emailTextField, 'ayan@qolarisdata.com');
      await tester.pumpAndSettle();

      await tester.enterText(passwordTextField, 'Test@123');
      await tester.pumpAndSettle();

      await tester.tap(loginButton);
      await tester.pumpAndSettle();

      // 断言跳转成功
      expect(dashboardPage, findsOneWidget);
    });

    testWidgets(
        '点击Subscribe按钮跳转到CheckoutPage',
        (tester) async {
      // 定位元素
      final subscribeButton = find.byKey(const ValueKey('Subscribe'));
      final checkoutPage = find.byType(CheckoutPage);

      // 断言初始页面按钮存在
      expect(subscribeButton, findsOneWidget);

      // 执行操作
      await tester.tap(subscribeButton);
      await tester.pumpAndSettle();

      // 断言跳转成功
      expect(checkoutPage, findsOneWidget);
    });
  });
}

内容的提问来源于stack exchange,提问作者Ayan Dasgupta

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 14:34:59