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.
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);
});
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With