Testing is one of the most important parts of project management. This phase allows the developers to go through all the possible cases that can take place when the project goes live. It also points out problems hidden in different features of the project. That’s the goal, right? To find the hidden bugs and solve them before the product goes in the market.
Manual vs Automated Testing
I consulted my friend, Areeq Azam, who is a Software Quality Assurance Engineer and here is what he had to say about the difference.
Manual Testing requires sole effort to execute the entire software testing life-cycle end to end, an Engineer identifies bugs with the help of the success rate of test execution manually. Whereas in Automation Testing, Engineers utilize the automation tools and programmable scripts having the support of various platforms to reduce the manual and redundant efforts having a minimum human intervention with the system.
Where do you do it in Flutter?
If you have ever created a Flutter project, you must have noticed a /test directory created by flutter create command. This is your playground for automated testing. See figure 1.
Automated Testing in Flutter
There are three types of automated testing in Flutter:
- Unit testing
- Widget testing
- Integration testing
We will explore each with one or more example from my sample projects.
Unit testing is performed to test functionality of a single component or a group of components. It helps identifying whether a particular piece of software code performs as expected.
Example of Unit test:
TextField and TextFormField widgets in Flutter have a validator parameter which takes a function. The function takes the text present in the textfield as the only parameter. If the function returns null, it means that there are no errors while returning a string indicates there is an error and the textfield then shows an error under it.
In figure 2, I used a TextFormField for an email address input, provided it a TextEditingController object, an InputDecoration object and a validating function.
The validating function was placed inside a class named EmailFieldValidator as shown in figure 3.
That was our setup. Now we head on to write a really simple unit test. Access the /test directory, there is already a dart file named widget_test.dart. This file is of no use for now. Create a new file named unit_test.dart.
Note: You can name it as you like but the extension should be .dart
Write the following code in the file as shown in figure 4. Don’t forget to import ‘package:flutter_test/flutter_test.dart’ on top of the file.
In the code shown above, we have a main(), two test() functions with a expect() inside each. Let’s break it down.
This is the entry point and you will run this using a small Run above it or by running the file itself. Running the main() means running all the tests at once while each test can be executed individually.
test() and expect()
These functions are provided by the flutter_test package. test() accepts a string identifier as description of test and a function without parameters where the logic of test is written.
In figure 4, we passed an empty string to our validate function, saved the resulting output in a variable. Then we passed it the expect() function which also takes an expected output along with the resulting output. It fails the test if outputs do not match. Now we run the tests and you can see the output in figure 5.
What happens when a test fails? Well, that is for you to find out. Tweak the above code, force the tests to fail and see for yourself.
You can write more tests for testing different functionality. Do not limit yourself to this example only. Trial and error will take you farther than you think.
We tested performance and reliability of the business logic written inside functions, methods and classes. In widget testing, as the name suggests, we test the behavior of widgets on mock data. Like if a Text widget will overflow on too much characters of data or whether a ListView widget would be able to hold the data coming from the network.
Widget tests are written almost like unit tests but they are async because widget building takes some time before we can provide the data to it.
Example of Widget test:
I made a login screen named LoginPage. The body and appbar of the Scaffold is not important for now as we will test LoginPage using widget testing. See fig 6.
Next, we move to the /test directory and create a file named login_page_test.dart. Write the code shown in figure 7.
Let’s break it down. The function testWidgets() is somewhat similar to test() except the fact that it is for widgets (Smart! right? :p). As discussed above, you can see the async keyword in the function’s signature. Now, we make an instance of our screen class and provide it to tester.pumpWidget(). This function takes a widget and inflates it.
Something is missing, isn’t it?
Yeah! the checking part.
Whether the widget inflated as expected can be checked by the expect() function but this it’ll take a type of widget as parameter this time. Explore more about widget testing here.
Integration testing is an advance and detailed topic which will be covered in the next article.
This is another cool function provided by the flutter_test package. Before, we could either run all the tests by running main() or only one test a time. With group(), you can put some tests inside it and can run independently of other tests you do not intend to run.