Unit Testing & Mockito
(Part 3 — Use of Mockito)
The last part of the article covers how to use Mockito in a proper way. We’ll follow the same example (Authenticator, SharedPreferencesManager and LoginViewModel) we talked about in the previous part.
There are a few limitations we have to consider when talking about Mockito, you cannot test:
- Inner classes.
- Final classes.
- Anonymous classes.
You only have to import the Mockito library and you’ll be able to use it in your Unit Test classes.
Integrate Mockito in your project
If you use Android Studio, you just have to import the libraries in your project Gradle file.
With androidTestCompile, the libraries won’t be include in the main project, just when you run your tests.
We import the library mockito core rather than mockito-all. This is because mockito-all is incompatible with JUnit 4.11! However mockito-core has a dependency on the hamcrest-core library, which is already embedded in JUnit 4. We therefore have to exclude the hamcrest-core library to prevent further issues.
Set up Mockito
Once you’ve included the libraries in your project, you can use it in your Unit Test methods. You can use the @Mock annotation to create mocks, avoid repetitive mock creation code and make the class more readable. For that you have to include the following piece of code in your setUp method.
Because we’re going to use real methods, probably at some point we will need the Android context, we can get it if we have extended our class from InstrumentationTestCase.
You can observe we’re modifying the properties of the system, so we have to write that piece of code regarding the cache. I believe this works because by default mockito tries to use the apps cache directory but we never run an activity so I suspect the directory is never created by the OS.
After that, we just have to support MockitoAnnotations with that line of code, if you forget it, the annotations won’t work. As you can notice, we defined three attributes in the Unit Test class, those which are mock have the “mock-” prefix. That’s the easiest way we can differentiate a mock object later on in our code. Let’s take a look now at what we can do with Mockito.
You can see all the details and methods in the documentation from its website. I’m going to focus on five of them (probably everything you want to do can be done with these ones): verify, spy, mock, doReturn and doThrow.
Mock and Spy: Use mock to create a mock object. Use spy when you want to use mocking methods in a real object.
The difference between them is that spyLoginViewModel has the properties of both mock and real object, so we’re calling a real object and we have the mocking methods available.
Verify: As its name says, you use this method to verify that a method has been executed once, more than one time or never.
doReturn: Use doReturn when you want to return a specific value when calling an object method.
doThrow: Use doThrow when you want to throw an exception when calling an object method.
Once we know the different methods we can use for our tests, let’s focus on how to integrate what we learnt during the previous parts of the article with Mockito.
Mock some classes
I recommend mocking the classes whose interactions with the rest of the project is complex (as we talked before). For example, a SharedPreferencesManager is going to have a lot of interactions with different parts of the Android OS.
If we use classes which are part of Android (or Java), we can trust them and mock them if their interactions are going to be difficult to manage, they should work properly.
BDD tests with NO real objects
One of the main problems of Mockito is that if you want to create a BDD test, we have to add a lot of extra code. This is because in a BDD test, we’re going to execute different methods from different classes and if we don’t specify that we want to call the real method, Mockito won’t call it (and we want all of them to be executed).
To handle this situation, we have two main methods available: doCallRealMethod() and doNothing(). We call doCallRealMethod() when we want that method to be executed (that means that you want that method to execute every line of code it has). doNothing() is useful when you want Mockito to omit that method because probably it’s too complex or it doesn’t affect the Unit Test execution.
Example with mock objects:
Same test with real objects:
And if we prepare the object in the setUp method, the unit test method is even simpler:
From the previous examples, we can see how readability improves when we use real objects. We also avoid a lot of boilerplate code using doCallRealMethod(), if we use that approach, we have to process all the interactions and if we miss one of them, the test will fail.
Combining real objects with Spy
As it was mentioned before, mocking methods are not available for real objects, so you cannot verify an interaction in a real object. For that, you should convert the real object in a spy.
After this article, you should be able to approach Unit Testing in a way it works for you. You can focus more on TDD, BDD or a mix of them. I tried to explain what works for me right now, it might not be that productive in the future or for different projects. But it’s a good approach we should have in mind.
Unit Test your methods by choosing the approach wisely, for certain classes TDD might be the best solution but not for others. Don’t spend too much time stuck with one approach if you can get the same result using another one.
The culture of the company shouldn’t affect how to unit test your code (managers forcing you to follow a classic TDD), it should be up to the developers what to do. Developers should be responsible for creating high quality code, if you can get that quality with your own approach, use it!
When new people join your development team, you have to make sure that the test coverage and the quality of the code is good enough and it matches what the rest of the team is doing at that moment. What I like to do is suggesting to the new person following as much as possible a kind of classic TDD with BDD approach. Once they adapt to the project and feels comfortable with it, then they can move to a better and most productive approach.
Thanks for reading.
Manuel Vicente Vivo