1: Unit Test — Testing in Flutter
Table Of Contents
- What is Unit Test?
- Diving deeper into Unit Testing
- Writing Unit Tests
- Mocking
- SetUpAll, TearDownAll and SetUp, TearDown Functions
What is Unit Test?
Before understanding Unit Testing in Flutter, we should learn what Unit Testing is. A unit test is a way of testing a unit that can be logically isolated in a system. Unit tests help us to verify the methods and components work as expected. Writing unit tests should be considered a part of the development phase and test all the situations that might be happening. Unit tests protect the changes of logic for all cases. Thanks to unit tests, you will not be worried about correctness when you change any logical components.
We can summarize unit testing for Flutter in 5 steps.
- The maintenance cost is low, and the execution speed is fast.
- Unit tests test a single function, method, class, or component.
- A unit test aims to verify the correctness of a unit of logic under different conditions.
- There are a few external dependencies, and they are generally mocked out.
- Unit tests generally do not read from and write to the disk, render to screen, and receive user actions from outside.
Diving deeper into Unit Testing
Unit tests should follow the Arrange-Act-Assert pattern.
ARRANGE: Setup what test needs
ACT: Call specific operation
ASSERT: Check the values
In the Arrange step, we created a counter instance. The default value of the counter is zero. In the Act step, we increased the counter value. And it should be one after the increase. In the Assert step, we expect that the counter value is one.
Writing Unit Tests
We use the test()
method for unit tests. It takes two parameters named description and body.
DESCRIPTION: It defines what does this method test. You should write a proper test description for understanding how, where, and what it tests.
BODY: Write your logic here based on the Arrange-Act-Assert pattern. It can be async if you need it.
We use the group()
method to group related parts of tests and see the test under the groups in the editor. It takes two parameters named description and body.
DESCRIPTION: It shows the context of the group.
BODY: Includes the related tests.
To start the tests and see results, you need to write this command to the terminal in the directory of the project folder.
flutter test
Before the test, I just want to show the part of the code below.
This BaseRepository
is our base class for repositories. It includes all the necessary properties and methods, and UserRepository
is extended from the BaseRepository
.
We are going to write the tests for them.
Mocking
Nowadays, almost all the applications are connected to servers. Servers store the data, do all the logical things, and send the response to the application.
In tests, we should not be dependent on external services or systems. Therefore, we should mock these dependencies. These dependencies might be a data source such as a data model or a package such as http.
For mocking, we use a package. I used to prefer mockito before the null safety, but I’ve started to use mocktail because of a few problems I got. In the examples, you will see the mocktail, but you are one hundred percent free to use which one you want. Add the package under dev_dependencies
.
dev_dependencies:
mocktail: ^0.2.0
After adding the package, we will be able to use Mock
and Fake
. When we implement a class, we also have to override the methods by the when. Thanks to extending the class with Mock
and Fake
, we do not need to override anymore. Let’s look at the example below. The body of the MockClient
is empty.
We created a class that implements Client
and extends Mock
. UserRepository
wants Client
as a parameter, and we passed the MockClient
. In this way, UserRepository
never accesses the real server.
We will see setUp()
and tearDown()
later a bit.
The next one is Fake
.
Fake helps us to define types for any()
. Then, we set the types in the registerFallbackValue()
method. For mocking methods, we are going to use the when()
method that comes with mockito and mocktail packages. It takes a method, and you can use one of thenReturn()
, thenAnswer()
, and thenThrow()
. If your method takes any parameter, the solution is the any()
method. The important part of the any()
method is that it has a few pre-registered values for types such as int
, String
and more. But sometimes you might need more types. In this kind of situation, you need to use the registerFallbackValue()
method to define. In this example, this is Uri
.
Now, this is the time to brief each line of this code.
We created a Mock
class for Client
to return custom responses and a Fake
class for Uri
to be able to access in any()
. We also registered the FakeUri
in setUpAll
by registerFallbackValue()
method. In the main()
method, we created two fields and set them in the setUp()
method. We defined a custom response body and response status code as fakeReponse
. In the when()
method, we gave the method which we want to mock and defined the return value. As you can see, normally client.get()
method takes an Uri
, but we passed any()
instead.
Then, everything is set. We can call the userRepository.getUsers()
method.
The expect method takes two required parameters.
ACTUAL: Takes the value we want to check
MATCHER: The validation part. Commonly, we prefer to use Matcher
s.
In this example, we want to check the result. So, we passed it here. Moreover, we expect that the type of the result is a List<User>
and the length is 2.
SetUpAll, TearDownAll and SetUp, TearDown Functions
setUpAll
setUpAll()
is a function that runs once before all the tests run. You can think of it as the initState()
method.
setUpAll(() {
client = MockClient();
userRepository = UserRepository(client: client);
});
tearDownAll
tearDownAll()
is a function that runs once after all the tests run. You can think of it as the dispose()
method.
tearDownAll(() {
userRepository.close();
client.close();
});
setUp
Sometimes, you need some preparation to register them to be called before each test run.
setUp()
is a function that runs before each test run.
setUp(() {
client = MockClient();
userRepository = UserRepository(client: client);
});
tearDown
tearDown()
is a function that runs after each test run. This is good for cleaning up after each test.
tearDown(() {
userRepository.close();
client.close();
});
Close Up
Other Articles in this series
🎉 Thanks for your time!
⭐️ I also would like to thank @ulusoyapps and @salihgueler to read and review this article.
💙 You can find me on Twitter and Linkedin. You can find everything about me here.
👉🏻 You can find all the source code on Github.