Widget Testing - The Untold Story
If you do not wish to test your Application, your customers also ain't gonna do so!
I strongly believe most people will accept the fact that testing your application is a prominent and integral part of your application building. Since our primary goal is to produce a robust and perfect product, testing becomes a key part before the product gets delivered to make it ideal.
Before diving deep into Flutter testing, I would like to discuss so general things about Development phase testing. I know most folks in our development community name the development phase testing unanimously as unit testing, but development phase testing not only succumbs to unit-level testing but has a lot more to do than that. Hence, I think naming it as 'Development Phase Testing' will give it the justice it deserves!
General Trivia on Testing
Most people in our developer community have this particular question rolling inside their heads.
I'm a busy Developer. How can I/Why should I?
Being a part of the community, I too ask the same question, but more than that, I think the best thing to do is to perform testing in the initial stages because Developing a product is an Art. It requires a lot of patience and strength to deliver the robust and ideal product as is expected. Hence, I strongly believe that it is a great practice to jot down test cases in the initial stages itself. By then you can ensure all your building processes are going fine step by step.
Flutter Testing: The Army
Now it’s time to get Fluttered. As everyone knows, Flutter has proven its power of building high quality, highly feasible UI apps and now has been growing as one of the giants in its own market. Testing Flutter apps is also one of the processes to build the sculpture properly from the preamble. To consummate the same, we have the following methods:
Unit Testing - Testing the bits and pieces of everything in the project. Here you will be testing every unit in the code like variables, methods etc.
Widget Testing - This is very special to Flutter. As you know, Flutter is made up of widgets. This helps us test the actions, reactions and functionalities of the widgets used.
Integration Testing - Integration testing refers to testing a process or flow. Here, we dive deep into automating the entire action play of the app features in all possible scopes available. There are some special packages similar to automation testing tools like selenium,appium etc.
Testing Rulesets
Know the rules better, so you can break them effectively - Dalai Lama
There are some general rulesets that you have to follow while writing the test cases. This gives you a better foundation, improved readability and clear structures. Those rules are listed below:
Folder Structures - All the files in the library folder for which the testing is to be performed is to be added with the same structure inside the test folder. Since all our test files are written inside a separate directory called test
, which is also a part of the Flutter project directory, we need to follow the same structure we do inside the lib
folder.
Naming Conventions - Naming the test files well is one of the key parts of the process. Every test file you are naming must be suffixed with the word '_test
' in order to let Flutter test package identify the test file and run the same and it also helps us to differentiate between the different files on its nature, like models, services or whatsoever it may be. After naming the file in the same method, you are able to run the entire test suite by running the command flutter test
from the root level of your project, which identifies and runs all the files ending with the phrase '_test.dart
'.
Packages
Now comes the helper hand - packages for testing. These packages help us to test each prominent features we are integrating with the Flutter application based on its functionalities:
test - This is a package used for testing native dart related components like methods and other components. This can be fetched from https://pub.dev/packages/test
flutter_test - This is an inbuilt library that comes with Flutter SDK. You can test all the specific components related to Flutter and it helps us to create an entire test suite that corresponds to test Flutter features. There is some example code for the flutter_test
package inside the example folder of Flutter official GitHub repository. One such can be referred here - https://github.com/flutter/flutter/blob/master/examples/hello_world/test/hello_test.dart
flutter_driver - Automation testing can be assisted through this package. This resembles the market pioneers like Selenium or Appium and works in the same way to test the Flutter application. This comes in play after the entire application is finished and when you have to create an automated suite that tests the permanent features after each release.
bloc_test - This is a special test package that helps us to test Bloc (the highly standard State Management technique). When a project is conceived with the Bloc State Management system, we can use this package to test the states emitted, order of states and the lot. The package link is https://pub.dev/packages/bloc_test
Implementation: The Live Show
Without further delay, we can get into the implementation of writing test cases for a working Flutter application.
For the demonstration, I have created a sample application which is a fancy version of the counter application which is popular among the Flutter dev community. A small demo of the app functionality is below:
In the application, we have three simple components - one text widget with the string "It`s Always success" and an IconButton
of heart shape which increments the number of likes received and shows it using the number of likes displayed. We use the setState({})
method to show the changes in the local state while clicking on the button and incrementing the number of hearts received.
Code Structure
Main method
Import Statements
Test Case -1: Finds One Title Widget: Positive Scenario
In this test case, we are going to check whether the title
widget is present are not. For this, we need to use the testWidgets
method. This method helps us to perform the Widget test to see if the title
widget("Its Always Success" - Text widget) is present or not. Contemporarily, the testWidgets
method also takes two parameters - one description string and an anonymous method inside where we write the test case execution scripts. This anonymous method takes up a parameter object - tester of class WidgetTester
. This is the key point of the test script which helps us to take the reference of test methods and execute all actions like drawing the virtual UI frames.
We will go through the steps one by one and see how a test case execution code is been sculptured:
Test Case -2: Finds One Title Widget - Negative Scenario
It's evident that for a Testing engineer, covering the negative scenarios is just as important as covering up the positive scenarios. Here in this test case, I will follow all the steps described for Test Case 1, except for the expect
method that we are trying to achieve. The ideology behind this test case is that now we have a widget Title which has the String "It`s Always Success" and we have proven it in the previous test case. What if we try to deceive the tester component by saying there is no such widget - expecting findsNothing
and passing the titleFinder
. Now actually the findsNothing
method will expect nothing of such reference passed to the left side (titleFinder) but the widget is already there. Hence you will see the test case failing and this is also a prominent confirmation that your app does not act out of box as expected.
Test Case -3: Testing the Icon Button Using Key - Positive Scenario
This test case defines finding the button available (The Heart IconButton
Widget). Here the difference is to refer the button using a Key - the boon of Flutter. Using a key, we can refer the widgets on a global scale and hence we will see how to get the widgets referred to using a key inside test scripts.
First of all, I have defined a separate file keys.dart
where I have defined the string values for all the keys I'm using to refer the widget as follows:
I have made the strings static so that I`m able to refer them globally.
Now inside the test script, I'm following the same steps described for the previous test cases. The change happens only at the place where we find the widget reference and here I`m using find.byKey()
method to get the widget reference using the key. I define a variable buttonKey
where I create a Key object by sending the static reference of the string we created- final buttonKey = Keys(Keys.icon_button);
. Now I'm passing the buttonKey
variable inside the find.byKey
method to get the expected reference of the heat IconButton
in the HomeScreen. final buttonFinder = find.byKey(buttonKey);
and finally we use expect
method and findsOneWidget
to see whether one widget is present in the HomeScreen and the test case passes.
Test Case - 4: Testing the Icon Button Using Key - Negative Scenario
As illuminated previously, we try to cover up a negative scenario for the previous case by expecting nothing using expectsNothing
widget for the widget which is already landed.
Test Case -5: Check Button Actions and find whether hearts are incremented - Positive Scenario
Its time to get into some action. Here, let's look into some of the actions taken on the HomeScreen
and test whether the re-actions are proper. Putting it in a statement, we are going to click on the heart Icon button and see whether the hearts are incremented and the number
widget adds up one on each click.
Group Test Running:
We can run the group method on top level to get the consolidated results of all the test cases described inside. In our case, 3 cases have passed (Positive scenarios) and 2 cases have failed(Negative scenarios), which is expected.
Running tests from terminal:
You can use the command flutter test
from the project root level to run all the files in your test
folder. This command will identify the files suffixed with "_test.dart
" and run the test cases enriched inside the files. The terminal result for our test script will be as follows:
An ideal test suite must give hope to the developers that they can rely on results. Hence, it's very important to carefully write the test cases and make sure it gives hope to make the application a robust one. In that way, development phase test script writing becomes one of the important features, not only for a Flutter project but for any application developed in any coding language.
The link for GitHub Repository for the complete Flutter project along with the test script is available in the testing branch here - https://github.com/devanandp/Scribblings/tree/testing.
Star the repo and give us a clap to invigorate us contributing to the technology world. Sending the readers a Thanks and 👋 until we meet next time!