如何模拟导航参数以测试Flutter屏幕小部件?

14

我希望为Flutter中的屏幕挂件编写Mockito测试。问题是,该挂件使用了导航参数中的一个变量,我不确定如何模拟这个变量。

以下是示例屏幕:

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

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

也许你们中的一些人知道如何最好地测试这样的屏幕小部件。


2
我的解决方案是不使用导航参数,而是将参数传递给构造函数。 您还需要使用自定义路由的push(),而不是pushNamed() - Mamo1234
我同意Lukas的观点,最好是通过构造函数传递参数。但我们实际上可以将它们结合起来,仍然可以使用导航参数,但是通过类构造函数(而不是通过上下文)接收它们,您可以查看这个视频教程https://www.youtube.com/watch?v=nyvwx7o277U。 - Alexa289
2个回答

10
我发现的方法与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 显示了它们,也无法找到它们。


请问,您解决了进度指示器的问题吗? - X09

4

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

因此,如果您不能像我一样使用构造函数,则可以直接在测试中使用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);
  });
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接