3 Tricks to Test Your Widgets with Flutter More Comfortably
Or what I learned about testing Flutter widgets by getting bumps
Young and bold
Flutter has managed to make a noise by claiming to produce natively compiled fast and beautifully designed applications for all the platforms, including mobile, from the same code base. However, even with that swift pace in our industry we’re all so keen on moaning about, it’s still a pretty young framework — as of 2020 it’s merely 3 years old (release 1.22.4).
As a typical youngster, the framework is prone to some obscurities. Particularly, its documentation, although very informative and even with some video lessons, still has quite a few blind spots that programmers are supposed to figure out on their own. To cap it all, an official package for testing Flutter widgets flutter_test features a rather confusing system of emulating asynchronous tasks and rendering components, making it likely to encounter some of its vague ideas. For this very reason, I’d like to present 3 tiny tricks that came to me as revelations, while trudging through the testing realm of Flutter.
pump vs pumpAndSettle
Flutter tests are based upon a fake system of rendering frames and performing asynchronous tasks for widgets passed to a method
pumpWidget of the
WidgetTester class: it draws frames as long as they are pumped manually by
WidgetTester. To put it simply, the whole test screen filled with widgets is frozen, and it’s our responsibility to thrust it forward.
Moreover, all the futures are also waiting for us to move the fake clock forward by passing the
duration parameter to the same method — in other words, all things are run synchronously. For instance, here we’re literally shifting a clock arrow by 100 milliseconds:
However, it implies that we should know how many frames we must pump consecutively. Otherwise, we’re met with a semi-finished rendering and are likely to fail the test. Luckily, there is another method
pumpAndSettle in the same class. It runs its counterpart
pump() periodically until there are no frames to draw, returning the number of pumped frames.
Now, there is a great lure for us, lazy developers, to apply
pumpAndSettle() everywhere instead of just
pump(). Unfortunately, we’re discouraged to do so by an official remark:
In general, it is better practice to figure out exactly why each frame is needed, and then to pump exactly as many frames as necessary. This will help catch regressions where, for instance, an animation is being started one frame later than it should.
Thus, they urge us into using
pump() to avoid excessive frames and therefore surplus work. Besides,
pumpAndSettle() always pumps at least once and by default sets the clock forward 100 milliseconds, even if there is no such a need.
Apart from that piece of writing, you’ll hardly come across any other elaboration on the issue, let alone any recipes as to how to know how many frames is enough for any individual case. For instance, navigation tends to require 2 pumps, whereas for animations the count varies from case to case. After trial and error, I’ve drawn a conclusion, that the best approach is to use both methods, but consciously. Use
- If it’s a standard animation after such actions of
- To figure out what number of frames there are to render your widget, or if it differs from what you expect. For instance, to draw a widget with
FutureBuilderit might take at least 2 pumps: the first for registering a future and the second for processing its result. After performing that analysis, it’s better to replace it with
pump()to not miss any anomalies afterwards.
For any other cases, it’s better to pump separately and with a certain number of duration if necessary. For example, if you mock getting some data from storage inside your widget and run
Future.delayed(Duration(milliseconds: new Random().nextInt(250))), then it’s logical to push the clock forward at least for the same amount of time:
runAsync — when lying is too deceptive
Thanks to the synchronous nature of Flutter tests, at times developers stumble upon a weird behaviour when they try to run asynchronous code beyond the context of pumped frames for widgets. In other words, the same
Future.delayed() will freeze a process if it’s called somewhere in your
testWidgets method, but not inside a widget, that’s passed to
Welcome one more useful method
WidgetTester! It serves to perform such truly asynchronous tasks as long as they can’t be pumped with your widgets:
Unfortunately, this aspect is not mentioned in introductory lessons on testing Flutter widgets yet. Moreover, it would even be quite complicated to comprehend the feature without knowing an entire picture of the Flutter synchronous approach. Although we can read about the mission
runAsync() undertakes in a brief explanation about the method itself, perhaps, it would be far better to highlight the functionality earlier in that very introduction. Otherwise, a programmer discovers that such a method exists through pain and StackOverflow instead of the official documentation.
find.byWidgetPredicate or searching for unsearchable
It’s a basic and well-known fact of Dart that it regards components of a library (functions, classes and their members, etc.) prefixed with underscore as hidden from other modules. However, sometimes we use such private parts without even knowing it. For instance, a deceptively looking constructor
FlatButton.icon in effect hides a factory producing an instance of a private class
_FlatButtonWithIcon derived from
Now, we could attempt to find such a widget in a conventional way to tap it:
find.byType(FlatButton). Unfortunately, it would retrieve no finders, since the method expects the precise
FlatButton type, whereas ours is unreachable
_FlatButtonWithIcon. As for
find.byIcon(), it returns merely an icon which won’t react to the
In such a case there turns up to be a more flexible approach:
Here we look for an ancestor of a widget with a standard icon
Icons.addas an example and the type of
FlatButton which also includes our dear
_FlatButtonWithIcon as a derived class. Thus, by combining several types of finders, we can even test widgets concealed in their libraries. The same pattern works likewise for a few more buttons with their own private classes produced by factories:
Obviously, programmers are destined to get more experienced through their own trials and errors however well-documented or mature technologies they exploit. Whereas smoothness of the process of learning can vary from tool to tool. In terms of the Flutter testing features, guys from Google have managed to make the entry level rather low, but some deeper conceptions should definitely get a more intent focus from official sources.
Even though this article is by no means an exhaustible tutorial, but rather a quick look at a few slightly neglected aspects, I hope it might still help clarify such tricky details of testing widgets with Flutter. After all, contributions from community are also an imperative part of active development for any ecosystem.