How to mock navigation arguments for testing flutter screen widgets?

5,058

Solution 1

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.

Solution 2

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);
  });
}
Share:
5,058
Daniel
Author by

Daniel

I am the administrator of ElderScrollsPortal.de and I'm working at OPITZ CONSULTING. In my free time, I also develop smaller projects.

Updated on December 12, 2022

Comments

  • Daniel
    Daniel over 1 year

    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.

  • X09
    X09 about 4 years
    Please, did you solve the issue of progress indicator?