Leaf: Flutter Social Media App — Part 3: Widget Tests

Shakleen Ishfar
5 min readNov 22, 2019

--

Hello good people! Hope you’re having a wonderful day! Last time we wrote our first few lines of Dart code to create a very basic UI.

Today I’m writing about

  1. Concept and importance of testing
  2. Widget tests
Yes we shall

Testing

Why test?

Testing is an important part of software development. It ensures that the application we create works the way it should be working. When the project becomes sufficiently huge it becomes hard to keep track of things. As a result when adding new features or changing existing ones, it is very hard to know whether the previously working feature got broken. So, we write tests to check the behavior. If the tests pass then the behavior of the feature is intact after modification. However, there could still be bugs not covered and tested by the tests themselves. That is why it is important to write proper tests and thoroughly test the application at every step of the way.

Tests in Flutter

There are many different variations of tests. However based on the scope of testing they are mainly categorized into 3 types which are

  1. Unit tests: Tests a single function or class.
  2. Component tests: Tests multiple functions or classes and how they relate.
  3. Integration tests: Tests the entire app or a huge chunk of the app.

Here’s a comparison taken directly from the official documentation

Different tests types and their properties

Quoting from the official docs

Generally speaking, a well-tested app has many unit and widget tests, tracked by code coverage, plus enough integration tests to cover all the important use cases.

Writing tests are easy. But knowing what to test requires planning and experience. Let’s write some tests for the simple app we have so far.

Writing tests

Leaf widget test

All our tests should be placed in the test folder in the root of our project directory. I like to separate out my test types into folders of their own named unit_tests, widget_tests and integration_test. The convention to name test files is pretty simple. Say you are writing a test for a function in file.dart, then the test file should be named file_test.dart. So let’s create main_test.dart inside the widget_test folder and write the following code:

Let’s go line by line.

Line 7 defines a test. The testWidgets() function is used to define tests. It takes two parameters. The first is a string use to specify either the name or scope of the test. The second is a function that actually tests the code. The function takes a WidgetTester object as input. This object allows the testing code to draw the widget being tested.

async and await keywords here are especially important. async signifies that this function is asynchronous. Meaning, the execution of this function will be done on a different thread. While the await keyword says that the testing code shouldn’t proceed beyond line 8, unless it finishes execution. If you don’t understand this don’t worry. We’ll revisit them in later posts.

Line 11 and 14 are codes that test the Leaf widget. Let’s go slowly here. find tries to find a widget using a mechanism (in this case byType()) and returns the result (a Matcher object) of the search. expect() compares this result with what it should actually be (In this case findsOneWidget which means only one widget should be found in both tests). [As for what a Matcher is and what it does isn’t necessary to understand. You can refer online to learn more about it if you want to.]

That’s it. That’s our simple test for Leaf widget. Similarly we can write one for HomePage widget as well.

HomePage widget test

Two things to note here

First is why we wrapped our widget in MaterialApp here, but not in the previous widget. The reason is that MaterialApp as a root is necessary for widgets lower down the tree. And since we’re testing widgets, they need the MaterialApp widget as a root. In the previous example, the widget Leaf is itself returning a MaterialApp, where in this case it isn’t. So the simple thing to remember is, if the widget doesn’t have a MaterialApp, then enclose it one. What would happen if we didn’t do so? Why not try running the test without including it. You’ll get your answer right away. (The test fails and gives you a huge error message. But the message says what’s wrong and how to fix it.)

Second is the findsNWidgets() function. We use it to check if there are N instances of a certain widget. In this case, we tested to see if there are 2 Text widgets in HomePage.

Running tests

VS Code: Go to debugging tab and start debugging. Gives the following output

Running tests through VS Code

Terminal: Type the following commands

$ flutter test test/widget_tests/home_page_test.dart
$ flutter test test/widget_tests/main_test.dart
Running tests through terminal

Committing and pushing our code

Before we finish this part, we’re going to commit our code and push it to GitHub. But we’re going to create a branch first. When in development, we work in a branch. When developing an app never ever push to main directly. When our feature or app is finalized, we will merge the branch with main. So, let’s create a branch, commit changes and push our work.

$ git checkout -b Home_Page_Dev # Create and switch to new branch
$ git add . # Stage all changes
$ git commit -m "Basic HomePage created" # commit changes
$ git push -u origin Home_Page_Dev # push changes

--

--