Welcome back to chapter 3 of Automating Tests in Flutter.
In this chapter, we are going to start with Unit Tests.
For unit testing to work, we need to have more logic than what I had so far, which is just all hard coding of the data in the app.
So first, let's talk about the project setup changes that I had to do.
I had to add few classes, nothing too complicated, but at the same time, it's good to know what each of them is trying to do.
First, we have a “screen” (house_dashboard_screen.dart) — HouseDashboardScreen
.
We are trying to do an MVVM (Model–View–ViewModel) model, so this is the View
part, the Screen
part.
NOTE
Model–View–ViewModel, also known as MVVM for short, is an established architectural pattern in software development. It’s structured to separate program logic and user interface controls. Flutter does not use or require a set design pattern so the developer must choose one — and MVVM is a great option. If you are unfamiliar with MVVM, there are links in the Resources.
So, it just calls the API, and has a structure to show the...
You can think of it like the widget structure to show the actual widgets on the app.
The Screen
is backed up by a ViewModel
— HouseViewModel
(house_view_model.dart).
You can think of a ViewModel
as the place where it's getting the data from. The Screen
only talks to the ViewModel
.
Now this view model isn't just directly calling the HTTP, I also added an additional layer called “repository”.
So, as you can see, it is calling the HouseRepository
(house_repository.dart).
This repository at this point has only one implementation of it.
It's an abstract
repository, meaning that you don't have any logic in it, but it only has one implementation.
But as the app matures, you can have multiple repositories.
Let's say right now, it's only getting the data from HTTP, the network, but you can also get the data from shared preferences like the local devices database, or somewhere else.
The job of the repository is to make sure that all this data is in sync, and if there are any transformations that you need to do, additionally, you can do that.
So, the HttpHouseRepository
(http_house_repository.dart) is the place where we are making the actual API call.
This is the actual API call, and we are passing the response
and then sending the data back to whoever called it.
So here the ViewModel
called it, so we are sending the data back to it.
Now, we also need something to represent the “house” (house_model.dart).
The House
has only 2 things — the house name (houseName
) and the score (score
).
So, this is the mapping from the JSON structure to our app.
Let's talk briefly about the JSON structure (house_api.dart).
The API that's calling is a mock server from Postman, which means that it's very easy to control what we are getting.
So, this is the JSON’s 4 values. Each of the 4 houses and their corresponding score.
And that's it.
Because it's coming from a mock server, there is no database backing to it, or like the backend logic. It's all controlled from this simple JSON structure.
For the purpose of this course, this is enough. And it helps us demonstrate the unit testing concepts. So, we are calling this API.
Let's see how the app looks like now.
We can do a hot restart and see...
We have the iOS simulator running here. And we have the 4 houses just corresponding to what we have here in our database, all 4 of them.
Each of them has a score corresponding to what we are getting back from the API.
And we are also computing a “place”. This is not coming from the API, but it's something that we are doing client side. The business logic. So, we are computing these places.
But as you can see the places don't look right. The 254 should have in the first place, and 250 has to be the last place.
Let's look at the code that's actually doing this logic.
I put this code in the model (house_model.dart), because it's a business logic. There is a method called sortHousesByPosition
which is essentially doing small things.
It takes a list of houses, sorts them in the order, and assigns them a position.
And after it sorts them, the lowest one gets the highest position. I think this is the place where we have some trouble. I can go ahead and fix it. That would have been a normal dev cycle.
But what we are going to do now is to first create a test case so that whatever we are doing is unit testable.
Let's start doing that by creating a branch.
NOTE
All the codes so far will be available in GitHub at this chapter (chapter_3_starting_point). So, if you're following this course by yourself on a terminal, you can clone the repo and then do git checkout chapter_3_starting_point
and you should be in the exact same spot that I'm here at this point before I added any unit test and fixed this bug.
So, let's start by creating a branch.
Because this is going to be the final one at the end of the chapter — git checkout chapter_3_end_point
(chapter_3_end_point).
Okay. Also, let's start...
We need to add a test here in our “test” folder. So, we are going to say New File, and say “house_model_test”.
One thing to note is that we are directly testing this logic, so there isn't necessarily anything to mock. This is the simplest unit test.
All we will do is instantiate the class, take some dummy data, and then use that to call the actual code and verify that it's actually doing what it's supposed to do.
We create this empty file and start writing out code.
We start at the main()
, and we start with the test('Verify sorting of houses', () { }
. Now we write the body.
So, let’s do the setup — “//Arrange”, “//Act”, and “//Assert”
So, let's create 3 houses.
I'm just creating the houses here so that we can use them for testing.
//Arrange
final house1 = House(houseName: 'first-house', score: 10);
final house2 = House(houseName: 'second-house', score: 5);
final house3 = House(houseName: 'third-house', score: 1);
List<House> houses = List<House>.from(<House>[house1, house2, house3]);
We created 3 houses; we created a list of houses.
Now let's do the “Act”; the actual test on it.
Let's create a new variable so that it's easier to sort houses — final sortedHouses
.
//Act
final sortedHouses = House().sortHousesByPosition(houses);
Okay.
And then in the assert statement, we just make a simple assertion.
//Assert
assert(sortedHouses.first.houseName == 'first-house';
Because this has the highest score, I would expect this to be the first one.
Okay, it is complaining about something. Yeah. I think it needs an Import
.
Okay, now let's run this test.
As I would expect, just like in the app where it's showing the wrong order, this test also fails.
Let's look closely at how we are doing the logic of sortHousesByPosition
.
What we are doing is we started them from low to high.
But when you're assigning the positions, we're assigning them from, again, high to low.
So, what we need here is fix that logic.
for (var i = 0; i < houses.length; i++) {
houses[i].position = houses.length - i;
}
Okay, so this is my first attempt. I'm subtracting “houses.length – i”.
Let's see how this works.
Go back to the test... We are not going back to the app. We are only going back to the test to verify this logic.
So, let's verify if that fixes it. Again, that didn't still fix it.
Let's also fill the other options out.
Okay. So, let's form this assertion, sorted houses, have one.
This position has to be.
We are not worried about the index, but we are only doing this based on the position.
//Assert
assert(sortedHouses[2].houseName == 'first-house' &&
sortedHouses[2].position == 1);
assert(sortedHouses[1].houseName == 'second-house' &&
sortedHouses[1].position == 2);
assert(sortedHouses[0].houseName == 'third-house',
sortedHouses[0].position == 3);
Okay, let's run this test now.
That's it. So, the first house should be in first position, second house in second, and third and third, right?
Let's see if this one actually fixed the app. Refresh the page, it fixed it.
So “Mineut” should be in first position. “Aria” is in third. “Crescendo” is two.
So, the places are fixed.
Even though there is some logic problem, like you would expect Minuet to go here in the first box. And Adagio should be in the fourth spot.
Let's do that part in the Widget test.