Testing Flutter Widgets for Beginners

Testing Flutter Widgets for Beginners

In this tutorial, we will learn how to write Flutter widget tests to ensure that every element on your mobile app screen is rendered correctly under various scenarios.

For this tutorial, we will have to create a simple flutter application and write a simple widget test for it. We will make use of the primary apis you can use to write simple widget tests.

Understanding Flutter Widgets

Flutter widgets are immutable descriptions of a Flutter user interface (UI). The actual user interface rendered by the render tree at specific locations on your phone screen is called an Element.

Widgets are abstract for the most part and contain the description and functionality while the element, take in these descriptions and creates render objetcs that renders the actual UI while managing every widget's lifecycle.

Elements contain more runtime information and handle updates. This is just the same principle that works for other UI frameworks.

What is Widget Test

Widget testing is a process that ensures a UI component functions as expected, even when it contains embedded methods. For example, consider a button that performs an asynchronous action and navigates to a new screen after 2 seconds.

If the button remains active and visible when it shouldn't be, the test would be considered a failure. Widget testing verifies both the behavior and state of the UI component to ensure it meets the expected outcomes.

Now that we have a little understanding of what widget testing is, let us delve into more detailed examples in the following sessions.

Testing Flutter applications

Lets create a simple flutter app with the following command:

flutter create widget_test

With the above command, you should get the default increment and decrement app or counter app.

This is the simple app we will be testing for this article. Consider widget tests as a simulation of how the user is supposed to interact with the app.

The widget test Apis allows us to access functions like finding UI components by text, by icons etc.

Code Example

void main() {
 testWidgets('Counter increments smoke test', (WidgetTester tester) async {
   // Build our app and trigger a frame.
   await tester.pumpWidget(const MyApp());

   // Verify that our counter starts at 0.
   expect(find.text('0'), findsOneWidget);
   expect(find.text('1'), findsNothing);

   // Tap the '+' icon and trigger a frame.
   await tester.tap(find.byIcon(Icons.add));
   await tester.pump();

   // Verify that our counter has incremented.
   expect(find.text('0'), findsNothing);
   expect(find.text('1'), findsOneWidget);
 });
}

The above code describes a simple test for the counter app. First, let us delve into each of the Apis and functions used in this test.

testWidgets: This function is used to describe a test. The function accepts a WidgetTester instance which allows us access to the apis like pumpWidget, tap, pump and more. With the WidgetTester instance, user action simulation is possible.

await tester.pumpWidget(const MyApp());

The above line of code creates a widget tree. With a widget tree simulated, you can find any small widget that you need to check for its expected behavior and the actual behavior it gives when there is an action.

The pumpWidget function is used to build custom StatelessWidget or StateFulWiget. This means if you created a widget called ClashOfClan(), you can use the pumpWidget to build that parent widget and it will build every other child component.

expect(find.text('0'), findsOneWidget);

Expect:The keyword expect means ( This is what I wrote the code to do and I expect it to act exactly how I want it to act).

Find: This is a globally available instance of the CommonFinders class. This allows you access methods that let you find widgets or smaller components within your widget tree.

Marchers: Marchers are verifiers. If I press the floating action button in our Flutter app, I expect that it increments by “1” and I want to verify that only one candidate is shown when the state is changed. If there are more than one text rendered, it throws an error like below:

Expected: exactly one matching candidate
  Actual: _TextWidgetFinder:<Found 2 widgets with text "0": [

If it finds exactly one text rendered, the test will pass.

Code Explanation

Step1: The code first builds a widget tree, called MyApp().

Step2: The next line of code verifies that the initial state of our app is at “0” and only one of that value is rendered on the screen.

Step3: The next line of code simulates a tap on the Floating action button (FAB). The pump method rebuilds the widget tree to reflect the new state.

Step4: After the action is carried out and rebuilt, we now need to verify that the values actually changed. Since we expect that after the FAB is pressed, there is supposed to be a rendered incremented value, we use the marcher to verify that the initial value is no longer zero “0” but now one “1”.

Conclusion

Congratulations on making it this far! In this article, we explored Flutter widget tests, the concept of elements, and the nature of Flutter widgets. We also examined the commonly used test APIs for writing tests, including testWidgets, pumpWidget, expect, and find. By understanding these concepts and tools, you can ensure that your Flutter applications behave as expected, providing a reliable and consistent user experience. Happy testing!