Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock navigation arguments for testing flutter screen widgets?

I would like to write a mockito test for a screen widget in flutter. The problem is, that this widget uses a variable from the navigation argument and I'm not sure how to mock this variable.

This is the example screen:

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

With this command I open the screen

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

Normally I would mock some components, but I'm not sure how to mock the screen arguments. I hope it works something like this. However, I do not know what I can exactly mock.

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

This is the current test, which of course is not working since _contact is null:

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

An ugly way would be to use constructor parameters for the screen only for testing, but I want to avoid that.

Maybe someone of you knows how to best test such screen widgets.

like image 587
Daniel Avatar asked Jul 12 '19 10:07

Daniel


2 Answers

If you are using a Dependency Injector such as I am, you may need to avoid pass contextual arguments to the constructor if your view is not built at the time the view class is instantiated. Otherwise, just use the view constructor as someone suggested.

So if you can't use constructor as I can't, you can solve this using Navigator directly in your tests. Navigator is a widget, so just use it to return your screen. Btw, it has no problem with Progress Indicator as pointed above.

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);
  });
}
like image 99
Paulo Henrique Nonaka Avatar answered Oct 05 '22 22:10

Paulo Henrique Nonaka


The way that I've found is the same approach how flutter guys are testing it: https://github.com/flutter/flutter/blob/d03aecab58f5f8b57a8cae4cf2fecba931f60673/packages/flutter/test/widgets/navigator_test.dart#L715

Basically they create a MaterialApp, put a button that after pressing will navigate to the tested page.

My modified solution:


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
}

This approach works ok-ish, had some issues with testing progress indicators as it was not able to find those even when debugDumpApp displayed them.

like image 29
Tomek Polański Avatar answered Oct 05 '22 22:10

Tomek Polański