A Deep Dive Into Widget Testing in Flutter : Part I (testWidgets() and TestVariants)

Understanding the Ins and Outs of Widget Testing in Flutter

Deven Joshi
Flutter Community
5 min readApr 9, 2021

--

I’ll be honest, I haven’t historically liked testing very much — it adds time to the overall development and sometimes adds overhead when making new changes to the codebase. It took more than a few bad PRs for me to go and actually study tests and everything that makes them work. But the funny thing is, past me would look very quizzically at present me when I say that I’ve actually started liking them.

I also haven’t been writing for a while — and this seemed like a great topic to return to writing with because elaborating on solving things that I consistently failed at before is how I started doing it in the first place.

Let’s get started.

What Really Is Widget Testing?

A Flutter app has many widgets for a variety of purposes. They have a specific layout and interactive elements with defined purposes. Over the course of the development of an app, several things in the app tend to get entangled. One sentence I love out of my unrelated love of physics is:

Does the flap of a butterfly’s wings in Brazil set off a tornado in Texas?

While the sentence talks about chaos theory, I like to think of it as a change you may think is unrelated to a part of your app may as well break it without even knowing. Widget testing provides an environment for you to test those parts of your app (usually individual widgets) to make sure the aforementioned tornado in Texas doesn’t make it to production on a Friday.

The usual practice is to write several groups of tests that test out various functionalities of the widget such as verifying the visual elements exist, interactive elements give the desired result, animations run, etc.

Additionally, you can also write ‘golden’ tests — tests that make sure that your widgets are still pixel-perfect after a few months of late-night code and over-caffeinated sprints. We’ll go into all of these, but first things first.

The Basics Of A Test

Tests by default are outside of the comfy cozy lib folder in their own test folder.

Widget tests run in the Flutter test environment and don’t actually run on a physical/simulated device — so keep in mind the restrictions that may come along with being executed as such.

Let’s look at the structure of a test:

The basics look pretty simple:

  1. The main() function seems to have tests inside it
  2. The testWidgets() function seems to hold a test
  3. Inside testWidgets(), we have a description for the test itself and a way to write the actual code for the test.

I’m not a person who would like to learn to drive a car without knowing how an internal combustion engine works. So let’s go deeper into this function and see what we uncover.

Looking Into testWidgets()

Because why not?

Let’s explore some things you can do with the testWidgets function.

Skipping the test entirely

Imagine a person, for a random name let’s call him Nash — asks you to write too many tests and you know some of them aren’t your best work, you can skip it entirely with the skip flag.

I mean, your test can’t fail if you skip it ¯\_(ツ)_/¯

Adding timeouts for a test

This is a slightly more tricky one since if you look into it, there are two parameters for setting timeouts for a test.

Without going into specifics of why it is this way, here is the gist of what is happening: The initialTimeout is the main timeout applied and can be increased but NOT MORE than the timeout parameter.

To increase the timeout we can use:

This adds time to the initial timeout but if we add more time than the timeout parameter, it won’t be counted.

Minor Detour: Exploring setUp() and tearDown()

Before we go onto one more thing testWidgets() provides, we need to know more about one convenience Flutter testing gives us: an ability to set stuff up before a test and clean up after.

We do this via the four functions provided to us:

setUpAll() and tearDownAll() are called just once each before and after execution of tests respectively. setUp() and tearDown() are called before and after EVERY test. These help us set up or clean up things.

One important thing to remember is that the same functions get called for every test.

Going Back To testWidgets(): Exploring Test Variants

Sometimes, one test needs to be run for several values, each requiring their own setup and cleaning but the same test code.

As a master of bad examples, let’s say we have three colors that we have to run the same test with. Let’s make an enum out of this:

Next, we create a test variant that allows us to run the same test for multiple values:

We see our familiar setUp() and tearDown() functions albeit with different params so that we can run setup for each value, but the critical thing here is the values getter.

Now, we can add the WidgetColor values to the variant:

This variant can now run the test for all the WidgetColor values. We can now supply this to our test via the variant parameter:

Running this one test will run it three times for all WidgetColor values:

Now that we know the basic function better, we can go explore testing in better detail… coming up in Part II.

PART II IS NOW OUT. Read it here.

That’s it for this article! I hope you enjoyed it, and be sure to follow me for more Flutter articles and comment for any feedback you might have about this article.

Feel free to check out my other profiles and articles as well:

--

--

Deven Joshi
Flutter Community

Google Developer Expert, Flutter | Technical Writer | Speaker