Flutter is a cross-platform app development framework by Google. In this article, I will talk about mocking dependent modules in unit tests using the
mockito library. If you are not familiar with testing in Flutter, I suggest you read this first.
What is mocking?
While testing, we mostly want to check if the code we wrote works. For unit testing specifically, we do not care if the internet is working or if the database available. We want to check functions in isolation.
But most often, it is not possible to get completely pure, non-dependent, functions. They will import other modules and libraries, which can be problematic or completely impossible to call in a testing environment. To get around that, devs came up with the idea of mocking.
Mocking is replacing a bit of system (a function, an import, anything) with a mock. A mock looks exactly like the part it is replacing, but it does not do anything at all. Using a mock, we are able to specify its return value and side effects, to test for all possible scenarios. Additionally, we are able to assert on a mock, adding another dimension to our testing scenarios.
This is a very simplified diagram of a dependency tree in a MVC application. Suppose we want to test the Controller class, which depends on the Model class, which depends on the Database. Since we do not have access to a database in our unit testing environment, we make a mock called MockModel, which will have exactly the same interface as Model, but will not perform any action on no database.
Adding the dependency
Mocking is made possible with the
mockito library, so you need to add it to your
dev_dependencies: mockito: 4.1.2
Check here for the latest version.
Mocking HTTP calls
One common use case for mocking is API calls. Suppose you have a function that queries the Car API for cars:
On lines 5-8 we define a
Car model, with a single field,
model. The function we want to test is
fetchCar. But, of course, making an HTTP call on every test run (which you should do very often) is inappropriate.
You may be wondering why are we passing the
client as an argument to the function. Why not just create it in the function itself? Well, this is one of the shortcomings of the
mockito library. While it creates excellent mocks, it does not allow to mock arbitrary imports. In other words, you must pass your mock via arguments/a setter/a DI solution to make them work. TDD practices become even more important in such case, as you have to design your code to be testable from line 1.
Now, in your test file, we can create a mock client:
It is that simple! First, import both the
mockito and the
http library, then define a class that
Client. So, every mock class has to extend
Mock and implement whatever class it is you are mocking.
mockito will take care of the rest. Now, to the test itself:
We have 2 tests here, to test if it works and test if it breaks. On lines 3 and 12 you can see the creation of the mock
client, nothing special there. Then, on lines
14-15 we set the target behaviour for the mock client. We say that
when(a certain action
);. In the first case we return a valid response to check (on line 8) if the function returns a car, and in the second case, we return an HTTP 500 to check if an exception is being thrown.
Thank you for reading, I hope you liked this article. Do you know any way to mock arbitrary imports in Dart? If yes, please let me know in the comments.