This article is my first blog post on Medium.
I’ve come to Flutter recently and this transition makes me learn a lot these days. To better summarise the topic, the idea of this article was born.
“Senpai, why unit testing is important?”
When developers compile, run, debug, what do they do?
They expect to see if their program behaves as expected. And with unit tests, they can do it automatically.
The main idea of unit testing is to take the system under control and cover all components of software with tests to get all benefits. Otherwise, it would be a waste of time.
Motivation is following:
- Reduced bug count
- Safe refactoring and reimplementation of code
- Positive impact on design
- Introduces knowledge of code base
A bit of application context
A replicant is a fictional bioengineered being in the 1982 film Blade Runner, and in its 2017 sequel Blade Runner 2049. The Nexus-series of replicants are virtually identical to adult humans but have superior strength, speed, agility, resilience, and intelligence, to varying degrees depending on the model. A replicant can only be detected by means of the fictional Voight-Kampff test, in which emotional responses are provoked and a replicant’s nonverbal responses differ from those of a human. A version of the test, referred to as a Baseline, is taken by K in Blade Runner 2049 to detect any mental or empathic damage, for which failure means retirement.
Let’s build a pseudo replicant and test it :)
Every replicant implements Survey. This is an abstract class that has two methods: ask a question and ask a math question. And every replicant has a Repository so to say “brains” that encapsulates work with two other entities NerworkInfo and RemoteDataSource.
At first, we are going to test that models inside our replicant’s brain are working as expected.
There are two test cases covered here. First, when calling the GET method returns Response with status code 404, the replicant has to catch an exception. Second, if the response has a status code 200 we expect to have an Answer model that matches the model created above in a group scope.
The second test case takes a little extra work, because of http.Response usually comes in form of a JSON object. To simulate a JSON object we can create an answer.json file with the corresponding structure we get from an endpoint. And then use a handy fixture method that converts JSON to the raw type string.
import 'dart:io';String fixture(String name) => File('test/fixtures/$name').readAsStringSync();
Also in expect method objects need to be compared somehow. Conforming to the equatable is a good way to go.
Worth to mention that there is a common pattern in unit tests —
AAA (arrange, act, assert). It is helpful to divide your test into three separate sections:
Arrange all necessary preconditions and inputs.
Act on the object or method under test.
Assert that the expected results have occurred.
Carry on and let’s test the second part of our Replicant’s brain. NetworkInfo with connection checker.
A class that
extends Mock should not have any directly implemented overridden fields or methods. These fields would not be usable as a Mock with verify() or when(). To get all functionality from the real object use it as a mixin for Mock.
The test above is following the same pattern but there was introduced another method verify() which checks that interaction is being made.
There are also other options to verify exact number of interactions:
Verify that function is called greater than number of times:
To ensure there was no interactions with the given mock:
To enforce the order of execution:
After all, let’s see if we are not dealing with replicants by testing humans.
How do humans differ from replicants? First, they are slower at math, that’s for sure. And second, they have feelings, that’s awesome!
In the snippet above we tested that humans sometimes are puzzled when solving math problems and also we are not cracking math problems instantaneously.
We have an understanding of how to write basic unit tests in Flutter using Mockito. If you are testing singleton objects, using reset() method on your mock is a good practice.