Mockito
Translated from German using DeepL.
Date: July 2021
Reading time: 3 minutes
Unit Testing
Mockito is used when unit tests are used. But what does that mean?
Unit tests are used to test individual modules in isolation. It is important that these classes have as few dependencies as possible.
Tools
There are several tools for carrying out such tests. Here are a few examples:
- JUnit
- NUnit
- TestNG
- ...
What is Mockito?
Mockito makes testing easier. With the help of the framework, components can be tested encapsulated. This also makes it possible to verify individual parts of a program with incomplete software. Further advantages are:
- Large StackOverflow community - Any issues are most likely already resolved.
- Popularity - Mockito was considered one of the top 10 Java libraries in 2013.
Integration
I use Spring Boot in my project. Therefore I can add Mockito as a dependency.
Maven
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.11.2</version>
</dependency>
Integration should not be a problem with other build systems either.
Gradle
repositories { jcenter() }
dependencies { testImplementation "org.mockito:mockito-core:3.+" }
Activate
In order to use Mockito, you must first inform the corresponding class. There are two variants:
Annotation
The @ExtendWith
annotation activates Mockito.
@ExtendWith(MockitoExtension.class)
Mocks Injecten
The @BeforeEach
annotation tells Mockito before each test that it should be used.
@BeforeEach
public void init() {
MockitoAnnotations.openMocks(this);
}
Usage
Mock
If Mockito is activated, you can benefit from it.
The most commonly used annotation is @Mock
. It makes it possible to create a class that offers all functions. However, these can still be manipulated.
MyService mock = Mockito.mock(MyService.class);
Mockito.verify(mock).foo(bar);
The following example shows that the list has been mocked. Elements can now be added to this mockedList
.
Later it is possible to manipulate this list.
@ExtendWith(MockitoExtension.class)
class TestClass {
@Mock
List<String> mockedList;
@Test
public void checkSize() {
mockedList.add("one");
}
}
This annotation is also used in the other examples.
Dummy
An object without implementation is referred to as a dummy. If the class to be tested is given a list of objects, these objects can be replaced by dummies. In this way, the class is not dependent on the functionality of the objects.
MyObject dummy = Mockito.mock(MyObject.class);
Stub
A stub can be used to manipulate the return value of a method. You can therefore determine what a method returns without knowing its implementation.
The following example tests how large the list is. On line 23, it is specified that the list always returns 100 when the size is requested. In the end, it is therefore no longer important how large the list really is. It therefore does not have to be filled for the test to pass.
@ExtendWith(MockitoExtension.class)
class TestClass {
@Mock
List<String> mockedList;
@Test
public void checkSize() {
mockedList.add("one");
Mockito.verify(mockedList).add("one");
assertEquals(0, mockedList.size());
// Stub
Mockito.when(mockedList.size()).thenReturn(100);
assertEquals(100, mockedList.size());
}
}
Although the list does not contain only 100 strings, the test does not fail.
Spy
If the class to be tested calls a service to perform an action, a spy can replace the service. This makes it possible to observe the objects.
List listSpy = Mockito.spy(new ArrayList());
listSpy.add(new Object());
Mockito.verify(listSpy).add(Matchers.any());
assertEquals(1, listSpy.size());
A Mock
is created based on the class of a type, not on an actual instance.
A Spy
wraps an existing instance. The difference is that you can track all interactions with a Spy.
Fake
A simplified implementation of a class is also known as a fake.
The class accesses a database. It does this via a service. This service can now be replaced by the fake implementation.
For example, this fake can have the data stored on its own and therefore does not have to fetch it from the database.
ArgumentCaptor
Parameters can be monitored with ArgumentCaptor
. If a method is called, it is possible to determine which attributes have been passed.
ArgumentCaptor<String> arg = ArgumentCaptor.forClass(String.class);
verify(mock).add(arg.capture());
String s = arg.getValue();
In this example, you can read the parameter from the string s
.
InjectMocks
@Mock
creates a mock. With the @InjectMocks
annotation, instances of a class can be created and Mocks
or Spies
can be injected into the class.
@Mock
Map<String, String> wordMap;
@InjectMocks
MyDictionary dic = new MyDictionary();
@Test
public void TestClass() {
Mockito.when(wordMap.get("aWord")).thenReturn("aMeaning");
assertEquals("aMeaning", dic.getMeaning("aWord"));
}
public class MyDictionary {
Map<String, String> wordMap;
public MyDictionary() {
wordMap = new HashMap<String, String>();
}
public void add(final String word, final String meaning) {
wordMap.put(word, meaning);
}
public String getMeaning(final String word) {
return wordMap.get(word);
}
}
Conclusion
In my opinion, the encapsulated tests make perfect sense. I now also understand why Mockito is so popular and was also used in our projects.