On your road to 100% coverage, you might end up needing to test a RefreshIndicator
. This small article aims at showing you how to do that and the common pitfalls to avoid!
The code to test
class MyHomePage extends StatefulWidget {
const MyHomePage({
Key? key,
}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String title = 'Hello';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: RefreshIndicator(
onRefresh: () async => setState(() {
title = 'Hey';
}),
child: ListView.builder(
itemBuilder: (_, i) => Text('$i'),
itemCount: 200,
),
),
);
}
}
As you can see, it's a simple ListView with a onRefresh
that change the title from Hello to Hey.
How to test
testWidgets('Should change title', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(const MaterialApp(home: MyHomePage()));
expect(find.text('Hello'), findsOneWidget);
expect(find.text('Hey'), findsNothing);
await tester.fling(find.text('1'), const Offset(0.0, 300.0), 1000.0);
await tester.pump();
expect(
tester.getSemantics(find.byType(RefreshProgressIndicator)),
matchesSemantics(
label: 'Refresh',
));
await tester
.pump(const Duration(seconds: 1)); // finish the scroll animation
await tester.pump(
const Duration(seconds: 1)); // finish the indicator settle animation
await tester.pump(
const Duration(seconds: 1)); // finish the indicator hide animation
expect(find.text('Hey'), findsOneWidget);
expect(find.text('Hello'), findsNothing);
handle.dispose();
});
There are several things in this test:
- First, you initialize the SemanticHandle; it will make checking that RefreshIndicator is in refresh state easier
- Then you pump your widget, and you check that you are in the correct starting state
- You then use
fling
to quickly swipe on the screen and pump several times to wait for the animation to settle. - Finally, you check that your title has been appropriately changed, and you dispose of the semantic handle.
Pitfall to avoid
I once got stuck on a test for a simple reason:
My content wasn't big enough to get scrolled
It's why I use testing to make sure that some edge cases I didn't think about are covered!
Since I wanted my user always to be able to refresh the list, even if there were a single element, I added a
physics: const AlwaysScrollableScrollPhysics(),
to my ListView, and my test was finally passing!
Conclusion
Thanks for reading through, and don't forget to reach me on Twitter if you have any other questions!
If you wanna read any other articles from my 100% coverage series, click below!