Flutter Widget Testing — Widget Keys

Madacode
3 min readFeb 16, 2023

--

Consistent way to look up for widgets.

In the previous article (https://medium.com/@madacode/flutter-widget-testing-intro-57fe7bb74f1f) we covered the basics of Widget testing and that includes looking up widgets by the text content of the given Widget or by the Widget type/class.

While it works fine for small widgets, there are some caveats:

  • What if the Widget’s textual content may change? (be it from external API data or from conditional logic);
  • What if we have more than one instance of the same Widget type in one Widget that we test? (e.g. multi section texts);
  • What if the copywriting of a given Widget textual content changes? The test will break for sure.
  • What if we changed some component’s Widget type but it still functions the same (e.g from TextButton to ElevatedButton)? The test will also break.

Based on those caveats, looking up for Widgets through its textual content and Widget type makes the Widget test brittle and prone to break to even slight changes.

Enter Widget keys, they give our widgets a unique ID that could be looked up later in widget test and integration test and provide a reliable and consistent way to look up for Widgets in tests (given the key values are unique), Widget keys are akin to HTML element IDs, where each HTML element has its own ID so it could be looked up by getElementByID or by CSS selectors.

Implementing Widget keys are easy since most Flutter build-in Widgets provide optional key parameter in the constructor. In this article we will explore how to implement them and improve our Widget tests.

Enough of intro, let’s practice implementing them.

First, grab a copy of the code from previous article here (https://github.com/aramadsanar/flutter_retrofit_example/tree/test/widgetTestsIntro) and let’s open up lib/ui/widgets/post_item_widget.dart file

Now, lets define a class named PostItemWidgetKeys and put our Widget keys there, the Widget keys we want to make are for the Text and ElevatedButton Widget:

class PostItemWidgetKeys {
static const Key textSectionKey = Key('text_section');
static const Key buttonSectionKey = Key('button_section');
}

Our Widget keys are defined with type Key and all of the definitions are static and const, this is to make the Widget key definitions accessible without instantiating an instance of PostItemWidgetKeys and making it a part of the compiled code (thereby making the pointers consistent).

Next, we apply those Widget keys to PostItemWidget, applying them is easy enough, just slap the appropriate key to the desired Widget:

import 'package:flutter/material.dart';
import 'package:retrofit_dio_example/core/models/post.dart';

class PostItemWidget extends StatelessWidget {
const PostItemWidget({
Key? key,
required this.item,
this.onClick,
}) : super(key: key);

final Post item;
final VoidCallback? onClick;

@override
Widget build(BuildContext context) {
final ButtonStyle style = ElevatedButton.styleFrom(
textStyle: const TextStyle(
fontSize: 20,
),
);
return Padding(
padding: EdgeInsets.all(16),
child: Column(
children: <Widget>[
Text(
item.title ?? '',
key: PostItemWidgetKeys.textSectionKey,
),
if (item.title?.isNotEmpty == true) ...<Widget>[
SizedBox(
height: 16,
),
ElevatedButton(
key: PostItemWidgetKeys.buttonSectionKey,
style: style,
onPressed: onClick,
child: const Text('See Detail'),
),
],
],
),
);
}
}

class PostItemWidgetKeys {
static const Key textSectionKey = Key('text_section');
static const Key buttonSectionKey = Key('button_section');
}

With Widget keys applied, let’s move on to the Widget test file, test/ui/widgets/post_item_widget_test.dart and make another Widget test case to look up for Widgets by Widget keys, simply copy the first Widget test case and modify it as such that it looks up Widget by Widget key. To use Widget keys we need to import the file where the Widget keys resides.

  testWidgets(
'PostItemWidget displays title and a button - find by Widget keys',
(WidgetTester tester) async {

},
);

Then, fill up the Widget test by pumping the Widget and making the assertions, to look up for Widget by Widget keys, use the method find.byKey with the desired Widget key as the parameter:

find.byKey(PostItemWidgetKeys.textSectionKey)

And the assertion methods remain the same as described in the previous article. Your Widget test shall look like:

testWidgets(
'PostItemWidget displays title and a button - find by Widget keys',
(WidgetTester tester) async {
// Build the widget
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: PostItemWidget(
item: post,
onClick: () {},
),
),
),
);

expect(find.byKey(PostItemWidgetKeys.textSectionKey), findsOneWidget);
expect(find.byKey(PostItemWidgetKeys.buttonSectionKey), findsOneWidget);
},
);

If you had done it correctly, you shall see the Widget key passes.

That’s all for Widget keys in Widget testing, you may see the full code here (https://github.com/aramadsanar/flutter_retrofit_example/tree/test/widgetTestKeys)

Thanks for reading and see you in the next article 👋.

--

--

Madacode

Senior Software Engineer @ Pinhome with 5+ years experience in various technologies including Go and Flutter. All posts are of my own opinion.