1: Unit Test — Testing in Flutter

Berat Göktuğ Özdemir
5 min readApr 5, 2022

--

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 Matchers.

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

  • Unit tests are low-cost and necessary to be sure of all the logic works as expected.
  • We should avoid working with real data-source anywhere. To avoid you can mock the classes and use when() method to change the logic of methods with mocktail or mockito package.

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.

--

--