Unit testing is expected to cover the smallest units of work, in Java this means classes and packages. Integration testing would cover the combination of modules. Regression testing is for maintaining quality after fixes and changes.
This Blog is about unit testing with "mocks", which are objects that simulate some environment like a database or a remote server. Mocks are useful for isolating tests from their environment that may be too unreliable or too slow for periodic automated tests.
The Mockito library has turned out to be the most popular helper for Java unit tests. Its responsibility is simulation, not assertion or running tests like JUnit does, although there is some overlapping.
I use the old JUnit 4 version for the examples in this Blog. JUnit 5 ("Jupiter") introduces new classes, methods and annotations.
Before we can try out Mockito we have to learn new words (or old words like "stub" in a new sense):
when(), given(), doReturn(), doAnswer(), doThrow()
, ...Create a Maven project structure
with following pom.xml
file:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>fri</groupId>
<artifactId>mockitoTest3</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.1.0</version>
</dependency>
</dependencies>
</project>
Then compile the project:
cd mockitoTest
mvn package
Then import it into your preferred IDE. For Eclipse you should either import it as "Maven Project" or launch mvn eclipse:eclipse
before importing it as "Java Project".
If you want to use the annotations of Mockito, you need to let Mockito read them by either a before-method:
import org.junit.Before;
import org.mockito.MockitoAnnotations;
import org.mockito.Mock;
public class MyMockitoTest
{
@Before
public void initMocks() {
MockitoAnnotations.initMocks(this);
}
@Mock
private List<String> mockedList;
....
}
or by defining a JUnit rule:
import org.junit.Rule;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.Mock;
public class MyMockitoTest
{
@Rule
public MockitoRule rule = MockitoJUnit.rule();
@Mock
private List<String> mockedList;
....
}
Annotations can be used outside method implementations. You don't need any initializer if you prefer to call Mockito explicitly like this:
import java.util.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import org.junit.Test;
public class MyMockitoTest
{
@Test
public void whenMocking() {
List<String> mockedList = mock(List.class);
....
}
}
Annotations are useful sometimes, but currently they are overused. Good source code is short, concise, and quickly readable, and annotations always need additional documentation reading. I would stick to static imports (like above) and explicit calls. That way you have everything in place.
All following tests have been written and checked to succeed (be green).
This example shows what a Mockito mock can do (without claim to completeness!). Copy & paste it into the class above.
1 | @Test |
Line 3 creates a mock object that simulates a List
interface. In line 6 we use it like a list by calling add("one")
. After that we call Mockito's verify()
to make sure that the method has been called once.
Now we would expect that the list mock contains "one", but line 12 asserts that it doesn't, the list is empty. This is because the mock can't know the behavior of a List
, we must teach it ("stub" it).
First we teach it in line 16 to always behave like it contains 100 items. In line 19 we teach it to always return "zero" when we call get(0)
on it, the same in line 22 with "one".
As we see, the add()
and get()
methods are not related in any way, like it would be with a real List
behavior. A mock is a very stupid thing.
Spies are a bit better than mocks. We don't need to teach them everything.
1 | @Test |
A spy is a kind of proxy, with the difference that it copies its "real" object. In line 3 we create a real List
object, in line 4 we wrap it into a spy. Again we call add("one")
, and verify that it has been called after.
Mind that verify()
is an assertion, and that it checks technical logic that may change frequently. Verifing makes sense in certain cases. In this example I kept it to show that a spy can be treated like a mock, not because I recommend to do it all the time and everywhere.
So, where is the difference to mock? We see it in ine 13. Here we assert that the spy list contains what we have added before. And we did not not teach (stub) it.
In line 17 we see that the real list is still empty. The spy uses its own private copy of the real object!
What more? In line 20 we stub the spy. We teach it to always return 100 for its size, although its real object would return 1 now. Line 21 asserts that this works. We can manipulate the behavior of the list through stubbing in any way. Line 23 makes it return a wrong list item for index 0. Whatever consequences this has on the adopted behavior.
In line 26 there is another difference to a mock. We can not teach it calls that are impossible to do on the wrapped real object, in this case a call to get(1)
. There is only one item in the wrapped list, so this causes an exception. First we must add another item, then we can stub get(1)
, like done in line 29 and 32.
A spy is a mock with behavior, additionally the behavior can be modified (stubbed). We should prefer the original behavior to stubbing, because we may be better off with a mock when needing lots of stubs on a spy.
This example is to demystify the word "captor".
1 | @Test |
Line 3 allocates a mock. We create a "captor" object for argument verification in line 4.
The lines 6 and 7 call the mocked add()
method two times, with different parameters. To find out now what were the parameters, we need to call verify()
like done in line 9, and pass the captor instead of real parameter values. After that, we can retrieve all parameter values from the captor, and assert them.
This was a glimpse into Mockito. Stubbing would need more examples, because this is used heavily. I will do this in subsequent Blogs.
ɔ⃝ Fritz Ritzberger, 2019-10-06