No one likes testing! Everyone thinks testing is a boring job. But trust me, testing a software is as important as developing a software.
Imagine, if you spend days and months building your app and finally when it went live, the app started crashing. What if you had tested all the functionalities before making it live?
So, testing is a very important phase of Software Development Life Cycle. Even if you hate it, you have to do it.
But, in this article i will make sure you will end up liking the process of testing.
So, what are you gonna learn in this article?
Just one thing - How to Unit test your code using Mockito!
Before i begin, if you wanna read about Unit Testing, check out my latest tweet which talks about a basic introduction to Unit Testing in Flutter:
https://twitter.com/gopinathanaswin/status/1470617070559711234?s=21
Now, lets get to the fun part.
What is Mockito?
Mockito is a package in Flutter that helps you create Mock Dependencies of classes while testing your code.
Let me give you an example:
Imagine a feature that you are testing requires data to be fetched from an external DB such as Firebase/Supabase.
When you are testing your code offline you cant possibly gather data from the DB right? Or even if you are online, you dont wanna wait for the actual data to return in order to test the app right?
So, what will you do then?
We use Mockito to create a fake class which will use dummy data as input for your tests !!
Before we get started with the code, lets add dependencies for two packages in pubspec.yaml :
dev_dependencies:
mockito: ^5.0.10
build_runner: ^2.1.2
Note: I have added the packages in the dev_dependencies
section. This is because, we are not gonna use these packages in the actual application code.
Also, the package versions may vary when you are reading this article. Do checkout pub.dev to get the latest versions.
Let's create a new file cat.dart
in the test
folder:
class Cat {
String sound() => "Meow";
bool eatFood(String food, {bool? hungry}) => true;
Future<void> chew() async => print("Chewing...");
int walk(List<String> places) => 7;
void sleep() {}
void hunt(String place, String prey) {}
int lives = 9;
}
I have used the example which is given in the official docs of mockito in pub.dev
Next, create a file cat_test.dart
file which will contain all the codes to test the class.
Add the following contents to the file:
import 'package:mockito/annotations.dart';
import 'cat.dart';
@GenerateMocks([Cat])
void main() {
}
@GenerateMocks([Cat])
is used to create a Mock Class for our Cat
class.
Now, head over to the terminal and type the following command:
dart run build_runner build
This will generate the mock file for us with the name cat_test_mocks.dart
which contains the MockCat
class which we are gonna use for our testing.
Import this new file into our cat_test.dart
file:
import 'cat_test.mocks.dart';
Now, head over to the main
function and initialise an object for class MockCat
.
var cat = MockCat();
Now, let's verify some methods. But, what is verify in testing ?
Verify: It means we are verifying a method if it was invoked in that particular scope or not.
I will explain this better with an example. Append the following code inside the main()
function:
test('verify sound',() {
when(cat.sound()).thenReturn('meow');
cat.sound();
verify(cat.sound());
});
We have defined a test block that performs verify operation on the sound()
method.
The first line:
when(cat.sound()).thenReturn('meow');
is known as Stubbing.
Oh come on! What is Stubbing now? I will explain this a bit later, but for now just imagine we have a method sound()
, that returns 'meow'.
Now, the second line:
cat.sound();
we invoke the sound()
method.
Finally, the last line:
verify(cat.sound());
we are verifying if the mentioned method with the given parameter list was invoked in this scope or not.
So, this test will pass since it was invoked in line 2. But what if we write verify like this:
verify(cat.sound('Bow'));
This will fail, because the sound()
method was not invoked in this scope with a String parameter 'Bow'.
So, this is how verify
works in mockito.
Where is this helpful ?
This is helpful to know whether a method was invoked during the flow of a unit.
For example: Verify if the validatePassword()
method was called while creating a user.
But, there are some more interesting variants of verify
known as verifyInOrder()
, verifyNever()
, and verifyZeroInteractions()
.
verifyInOrder() takes in a list of methods, to verify if they were invoked in that particular order.
verifyNever() takes in a method, to verify if it was never invoked.
verifyZeroInteractions() takes in a mock object (not a method), to verify if that object was never called in the scope of the test.
Now, lets jump into Stubbing.
Stubbing is the process of overriding the behaviour of a method belonging to the mock class instance.
Example:
Consider a method that returns 'Dart':
String getLang() => 'Dart';
We stub the method to return 'Flutter' instead.
when(obj.getLang()).thenReturn('Flutter');
Now, no matter how many times you call the method getLang()
, it will always return 'Flutter' as opposed to its declaration in its class.
This is helpful when you wanna test your methods for different return data.
Let's consider our MockCat
class and test a stubbing:
test('stubbing',() {
when(cat.sound()).thenReturn('Flutter');
expect(cat.sound(), 'Flutter');
});
In the normal flow cat.sound()
should return 'Meow', but after stubbing it will always return 'Flutter'.
If a method invocation matches multiple stubs, the one which was declared last will be used. It is worth noting that stubbing and verifying only works on methods of a mocked class; in this case, an instance of MockCat
must be used, not an instance of Cat
.
Best Practices while working with Mockito
Whenever you make changes to the class that you are mocking, always re-generate the mock class using the build command that i mentioned above.
Always Stub your method before verifying it in a test block. Otherwise, it will return an error like the following:
- Testing with real objects is preferred over testing with mocks - if you can construct a real instance for your tests, you should!
If you liked this article, dont forget to hit the like button and share it with your friends and colleagues.
Follow me on Twitter @GopinathanAswin where i regularly share my learnings in Dart and Flutter 💙
Top comments (0)