Explanation of the Robot Pattern UI Testing Library in Flutter

Ryoichi Izumita
5 min readDec 23, 2023
Photo by Boitumelo on Unsplash

The Robot pattern is one of the software testing methods that encapsulates user actions as a series of steps and defines them as reusable classes. This allows for efficient simulation and testing of user interactions with the application, which can help improve the quality of the application.

In this article, I will introduce the Robot pattern and the Robot library I developed. This library is for using the Robot pattern in Flutter, and you can create Robot classes for testing each widget. By using the Robot library, you can improve the reusability and efficiency of tests, ultimately improving the quality of the application.

Robot Pattern

The Robot pattern has the following features:

  1. It encapsulates a user’s actions as a series of steps
  2. It defines this as a reusable class
  3. It efficiently simulates and tests user interaction with the application

In Flutter’s Widget testing and Integration testing, you test widgets using APIs such as WidgetTester and find. However, these APIs can make the test code complex and potentially reduce the reusability and efficiency of the tests. For example, the test code tends to become complex as shown below.

testWidget('my test', (tester) async {
await tester.pumpWidget(MaterialApp(home: MyWidget()));
await tester.tap(find.byKey(const Key('my_widget_key')));
await tester.pumpAndSettle();
expect(find.text('Hello, World!'), findsOneWidget);
});

This is an example of a test that does not use the Robot pattern. First, display MyWidget using the pumpWidget method. Then, tap the widget with a specific key. After that, wait until the state of the widget stabilizes using the pumpAndSettle method. Finally, confirm that specific text is being displayed using the expect method.

pumpWidget may require MaterialApp, Material, Scaffold to display MyWidget. Also, Riverpod code may be needed to display MyWidget. Setting the state of MyWidget will also be necessary. pumpAndSettle waits until the state of MyWidget changes, but it’s not the essence of the test. What we want to test can be thought of as follows:

  • When MyWidget is displayed
  • When the button is tapped
  • It displays ‘Hello, World!’

Also, in tap and expect, we find and use Widgets with the specified keys and text, but the readability and reusability are low.

By using the Robot pattern, you can write test code simply as follows.

testWidget('my test', (tester) async {
final robot = MyWidgetRobot(tester);
await robot.show();
await robot.tapHelloWorldButton();
robot.expectHelloWorldAppears();
});

In this test code, we are using the MyWidgetRobot class. This class defines methods for displaying MyWidget, tapping buttons, and verifying the display of ‘Hello, World!’. By using Robot, we can clearly define the purpose of the test (What) and conceal the method of the test (How). This makes the test code easier to read and reuse.

Robot Library

The Robot library is a library for using the Robot pattern in Flutter. The Robot library provides a Robot class for testing each widget. It makes it easier to create a class that describes the ‘How’ of testing.

Installation and Setup

The installation and setup of the Robot library is very simple. The steps are as follows:

  1. First, add the robot package to the pubspec.yaml file.
dev_dependencies:
robot: ^0.0.4

2. Next, run `flutter pub get` at the root of the project to install the packages.

3. Finally, import the `robot` package into the test code.

import 'package:robot/robot.dart';

With this, the basic setup of the Robot library is complete. Next, I will explain the specific usage methods.

Basic Usage Method

Here are the basic steps to write tests using the Robot library:

  1. First, for each widget you want to test, create a class that extends the Robot class.
class MyWidgetRobot extends Robot<MyWidget> {
MyWidgetRobot(super.tester);

late String text;

Finder get textFinder => find.descendant(of: this, matching: find.text(text));

Future<void> show() => tester.pumpWidget(MaterialApp(home: MyWidget(text: text)));

void expectText() => expect(textFinder, findsOneWidget);

Robot is a subclass of Finder, and MyWidgetRobot can also be used as a Finder. Therefore, by doing `expect(this, findsOneWidget)` within MyWidgetRobot, you can verify whether MyWidget exists. This is why you can pass ‘this’ to the ‘of’ in find.descendant.

The text field is the data passed to MyWidget.

textFinder, show, and expectText are hiding the How and providing the What as an interface.

2. Next, create a test file. In the test file, import the Robot class of the widget and the test package of Flutter.

import 'package:flutter_test/flutter_test.dart';
import 'package:robot/robot.dart';

import 'my_widget_robot.dart';

void main() {
testWidgets('MyWidget should show text', (tester) async {
final robot = MyWidgetRobot(tester)..text = 'Hello, World!';
await robot.show();
robot.expectText();
});
}

The above is the basic procedure. By extending the Robot class, you can define your own tests for specific widgets. Also, the Robot class is extendable and can be customized to meet specific test requirements.

Next, I will introduce more advanced usage examples and techniques.

Advanced Use Cases and Techniques

The Robot library not only supports basic usage, but also more advanced testing scenarios. Below, we introduce some advanced usage examples and techniques.

  • You can use the Robot.byKey constructor to test widgets with a specific key.
  • You can use the Robot class as is.
final robot = Robot<Text>.byKey(tester, const Key('count'));
robot.expectText('1');

You can create a Text Robot for Key(‘count’) using Robot as is, and you can check the display with expectText. It is also possible to create an instance of `Robot<CustomWidget>` for your custom widget. To create things like expect for them, you can do so with `extension on Robot<CustomWidget>`.

  • Check display of the widget: You can use the expectShown/expectHidden methods to check the display of the widget.
robot.expectShown();
robot.expectHidden();

In addition to the above, several methods that support tests have been implemented. We plan to increase this in the future, but if there are any methods you would like us to incorporate, we look forward to your Issues and Pull Requests.

By utilizing these advanced use cases and techniques, it becomes possible to efficiently implement more complex test scenarios. For detailed usage and details of each method, please refer to the documentation of the robot package.

Advice

  • Detailing the Robot pattern may potentially make the Robot’s maintenance difficult. It is crucial to create Robots at an appropriate level of granularity.
  • You should avoid describing ‘How’ using branches within the Robot. If branching becomes necessary, we recommend reconsidering the implementation and testing.
  • You should avoid having the Robot’s state within the Robot. Holding a state could potentially reduce the reusability of the tests.

Conclusion

In this article, we have explained the detailed usage of the Robot pattern and the Robot library. By utilizing the Robot pattern, you can improve the reusability and efficiency of tests, thereby enhancing the quality of your application. The Robot library is designed to easily implement this pattern in Flutter, making your test code more readable and reusable.

We plan to continue expanding the functionality of the Robot library and support more test scenarios. If you have any comments or requests, please let us know. We would be delighted if this library could contribute to improving the testing of many projects.

--

--