Test Driven Development is a major skill for any Android Developer. It saves debugging time, prevents users from running into bugs and keeps the app easy to maintain among many other benefits.
I knew that there are 4 big technologies I had to learn if I want to make it in the test-driven world: JUnit, Espresso, Mockito, and Robolectric.
Photo from Google IO 2018 Frictionless Android Testing
This is about my start on learning Mockito.
First things first… finding proper tutorials for Mockito is a huge pain in the ass! Everyone’s got some in-depth tutorials on Mockito but nearly every single one of them either miss out on Mockito fundamentals I was yet to learn or started with a complex app that I could not bother to understand half-past midnight.
So this one’s for all of you out there who are like me, who just want the basic fundamentals of Mockito. We can both get to the complex gritty stuff after that.
So what is Mockito
Mockito is a Unit Testing Library that lets you mock objects and verify the behavior of their methods. Why mock you may ask?
Methods in Android as we know them make use of dependencies from all sorts of sources. This is bad for testing. With Mockito however, we can test the logic in these methods in isolation without calling real implementations of those dependencies. As the name implies, we mock those implementations.
Import the Dependency
Stick this in your app/build.gradle
testImplementation 'org.mockito:mockito-core:1.10.19'
Initialize Mockito (@RunWith)
In order to load Mockito in your tests, you have to annotate your test class with @RunWith.
@RunWith(MockitoJUnitRunner::class) class ExampleUnitTest { ...
I also recommend importing all of Mockito’s static methods to allow you to use keywords like ‘when’ just that bit more seamlessly.
Mockito Annotations
@Mock
This is the bread and butter of Mockito. Mocking an object in conjunction with verify can let us confirm that object’s methods follow the behavior and return the values we expect it to. This is done without changing the mocked object. It’s practically immutable.
@Mock lateinit var mockedList: ArrayList<String> @Test fun whenUseMockAnnotation_thenMockIsInjected() { mockedList.add("one") Mockito.verify(mockedList).add("one") assertEquals(0, mockedList.size) `when`(mockedList.size).thenReturn(100) assertEquals(100, mockedList.size) }
After adding “one” to the array list, we can verify that it was indeed added, but outside of verifications, the list size stays at a constant 0. No real implementations of the add method were called.
‘When’
If you noticed ‘when’ up there, congratulations. We can make specific functions return a fixed value when they’re invoked during the test. In the above example, it’s a bit pointless, but you’ll need it when you write more in-depth tests where you need specific actions to change some values whilst mocking before testing the other part of a function.
`when`(mockedList.add(("one"))).thenReturn(true) assertEquals(mockedList.add("one"), true)
@Spy
@Spy does the same thing as @Mock but with one key difference: spied objects are partially implemented and thus, real methods are invoked. In layman’s terms, spied objects are mutable within the test functions. Just like mocks, spied objects can still be verified and stubbed.
@Spy lateinit var spiedList: ArrayList<String> @Test fun whenUseSpyAnnotation_thenSpyIsInjected() { spiedList.add("one") spiedList.add("two") Mockito.verify(spiedList).add("one") Mockito.verify(spiedList).add("two") assertEquals(2, spiedList.size) doReturn(100).`when`(spiedList).size assertEquals(100, spiedList.size) }
@Captor
@Captor is used when you want to capture values during verification… Just read the code. You’ll get what I mean.
@Captor lateinit var argCaptor: ArgumentCaptor<String> @Test fun whenUseCaptorAnnotation_thenTheSam() { mockedList.add("one") Mockito.verify(mockedList).add(argCaptor.capture()) assertEquals("one", argCaptor.value) }
@InjectMocks
Say you have a Dictionary object that takes in a Map<String, String> in its constructor. If you want to test the Dictionary with a mock of the Map, you can use @InjectMocks to, well, inject a previously created mock of the Map into the Dictionary.
@Mock lateinit var wordMap: Map<String, String> @InjectMocks var dic = MyDictionary() @Test fun whenUseInjectMocksAnnotation_thenCorrect() { `when`(wordMap.get("aWord")).thenReturn("aMeaning") assertEquals("aMeaning", dic.getMeaning("aWord")) }
You can inject spies as well, but not in the same way. Trying to do so will result in an exception. Instead, you can use the @Before annotation to initialize the Dictionary beforehand.
@Mock lateinit var wordMap: Map<String, String> lateinit var spyDic: MyDictionary @Before fun init() { MockitoAnnotations.initMocks(this) spyDic = spy(MyDictionary(wordMap)) }
Conclusion
I hope that was basic enough for you. None of that MVP jagger that’s just way too much for learning something simple. If you haven’t already noticed, these examples were taken from Baeldung’s post on the same topic. Out of all the tutorials I’ve been through myself to learn this, his was the only one that actually helped, and he’s got a whole course on Mockito. I recommend you go check him out.