如何模拟用于测试颤动屏幕小部件的导航参数?

Posted

技术标签:

【中文标题】如何模拟用于测试颤动屏幕小部件的导航参数?【英文标题】:How to mock navigation arguments for testing flutter screen widgets? 【发布时间】:2019-11-22 03:11:55 【问题描述】:

我想为 Flutter 中的屏幕小部件编写一个模拟测试。问题是,这个小部件使用来自导航参数的变量,我不知道如何模拟这个变量。

这是示例屏幕:

class TestScreen extends StatefulWidget 
  static final routeName = Strings.contact;

  @override
  _TestScreenState createState() => _TestScreenState();


class _TestScreenState extends State<TestScreen> 
  Contact _contact;

  @override
  Widget build(BuildContext context) 
    _contact = ModalRoute.of(context).settings.arguments;

    return Scaffold(
      appBar: AppBar(title: Text(Strings.contact)),
      body: Text(_contact.name),
    );
  

用这个命令打开屏幕

Navigator.pushNamed(context, TestScreen.routeName, arguments: contact);

通常我会模拟一些组件,但我不确定如何模拟屏幕参数。我希望它能像这样工作。但是,我不知道我能准确地模拟什么。

when(screenArgument.fetchData(any))
    .thenAnswer((_) async => expectedContact);

这是当前的测试,因为 _contact 为空,所以它当然不起作用:

void main() 
  testWidgets('contact fields should be filled with data from argument', (WidgetTester tester) async 
    // GIVEN
    final testScreen = TestApp(widget: TestScreen());

    // WHEN
    await tester.pumpWidget(testScreen);

    // THEN
    expect(find.text("test"), findsOneWidget);
  );

一种丑陋的方法是使用屏幕的构造函数参数仅用于测试,但我想避免这种情况。

也许你们当中有人知道如何最好地测试此类屏幕小部件。

【问题讨论】:

我的解决方案不是使用导航参数,而是将参数传递给构造函数。您还必须使用带有自定义路由的 push() 而不是 pushNamed() 我同意 Lukas 的观点,最好通过构造函数传递参数。但我们实际上可以组合它,我们仍然可以使用导航参数但通过类构造函数接收它(不是从上下文中),你可以在这里查看这个视频教程youtube.com/watch?v=nyvwx7o277U 【参考方案1】:

我发现的方法与 Flutter 开发人员测试它的方法相同: https://github.com/flutter/flutter/blob/d03aecab58f5f8b57a8cae4cf2fecba931f60673/packages/flutter/test/widgets/navigator_test.dart#L715

基本上他们创建一个MaterialApp,放一个按钮,按下后将导航到测试页面。

我修改后的解决方案:


Future<void> pumpArgumentWidget(
  WidgetTester tester, 
  @required Object args,
  @required Widget child,
) async 
  final key = GlobalKey<NavigatorState>();
  await tester.pumpWidget(
    MaterialApp(
      navigatorKey: key,
      home: FlatButton(
        onPressed: () => key.currentState.push(
          MaterialPageRoute<void>(
            settings: RouteSettings(arguments: args),
            builder: (_) => child,
          ),
        ),
        child: const SizedBox(),
      ),
    ),
  );

  await tester.tap(find.byType(FlatButton));
  await tester.pumpAndSettle(); // Might need to be removed when testing infinite animations

这种方法工作正常,但在测试进度指示器方面存在一些问题,因为即使 debugDumpApp 显示它们也无法找到它们。

【讨论】:

请问,进度指示器的问题解决了吗?【参考方案2】:

如果您使用像我这样的依赖注入器,如果在实例化视图类时未构建视图,您可能需要避免将上下文参数传递给构造函数。否则,只需按照某人的建议使用视图构造函数。

所以如果你不能像我一样使用构造函数,你可以直接在测试中使用 Navigator 来解决这个问题。 Navigator 是一个小部件,因此只需使用它来返回您的屏幕。顺便说一句,上面提到的进度指示器没有问题。

import 'package:commons/core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';

class MyCustomArgumentsMock extends Mock implements MyCustomArguments 

void main() 
  testWidgets('indicator is shown when screen is opened', (tester) async 
    final MyCustomArguments mock = MyCustomArgumentsMock();

    await tester.pumpWidget(MaterialApp(
      home: Navigator(
        onGenerateRoute: (_) 
          return MaterialPageRoute<Widget>(
            builder: (_) => TestScreen(),
            settings: RouteSettings(arguments: mock),
          );
        ,
      ),
    ));

    expect(find.byType(CircularProgressIndicator), findsOneWidget);
  );

【讨论】:

以上是关于如何模拟用于测试颤动屏幕小部件的导航参数?的主要内容,如果未能解决你的问题,请参考以下文章

导航抽屉未在颤动中从 APPBar 小部件打开

每次打开它时我都想要新的(新鲜的)颤动页面小部件

如何在颤动的屏幕上显示一个小部件?

在小部件构建期间如何在颤动中导航?

如何在颤动中截取屏幕之外的小部件屏幕截图?

如何在颤动中将数据从一个小部件传递到另一个小部件?