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.
Terms
Before we can try out Mockito we have to learn new words (or old words like "stub" in a new sense):
- Mock:
Object that simulates another object of which it knows the class or interface, but not its behavior, this must be "stubbed" - Spy:
Object that copies a given object, or instantiates a given class, and forwards all calls to that object, so it simulates also its behavior; the behavior additionally can be "stubbed" - Captor:
Helper that is able to reveal arguments passed to some mock method - to stub:
To teach a mock or spy how to behave when some method is called on it, i.e. usingwhen(), given(), doReturn(), doAnswer(), doThrow()
, ... - to verify:
To check whether a method was called with certain arguments, never, once (default), or a given number of times
Usage
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".
Preparations
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.
Example Tests
All following tests have been written and checked to succeed (be green).
Mock
This example shows what a Mockito mock can do (without claim to completeness!). Copy & paste it into the class above.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | @Test public void whenMocking() { List<String> mockedList = mock(List.class); // call a method mockedList.add("one"); // verify that the method has been called once verify(mockedList, times(1)).add("one"); // why would I want to verify that a method has been called when I call it myself? // the mock doesn't behave like a list! assertNull(mockedList.get(0)); assertEquals(0, mockedList.size()); // we must teach a mock how to behave when(mockedList.size()).thenReturn(100); assertEquals(100, mockedList.size()); when(mockedList.get(0)).thenReturn("zero"); assertEquals("zero", mockedList.get(0)); when(mockedList.get(1)).thenReturn("one"); assertEquals("one", mockedList.get(1)); } |
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.
Spy
Spies are a bit better than mocks. We don't need to teach them everything.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | @Test public void whenSpying() { final List<String> realList = new ArrayList<>(); List<String> spiedList = spy(realList); // call a method spiedList.add("one"); // verify that the method has been called once verify(spiedList, times(1)).add("one"); // why would I want to verify that a method has been called when I call it myself? // the spy behaves like a list! assertEquals("one", spiedList.get(0)); assertEquals(1, spiedList.size()); // but the real list is not affected, the spy works on a copy! assertEquals(0, realList.size()); // we can teach a spy how to behave when(spiedList.size()).thenReturn(100); assertEquals(100, spiedList.size()); when(spiedList.get(0)).thenReturn("zero"); assertEquals("zero", spiedList.get(0)); // when(spiedList.get(1)).thenReturn("one"); // this would throw an IndexOutOfBoundsException, because we // can not teach the spy something the real object can't do yet spiedList.add("two"); assertEquals("two", spiedList.get(1)); // now we can stub the call when(spiedList.get(1)).thenReturn("one"); assertEquals("one", spiedList.get(1)); } |
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.
Captor
This example is to demystify the word "captor".
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Test public void whenCapturing() { List<String> mockedList = mock(List.class); ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); mockedList.add("one"); mockedList.add("two"); verify(mockedList, times(2)).add(captor.capture()); List<String> arguments = captor.getAllValues(); assertEquals("one", arguments.get(0)); assertEquals("two", arguments.get(1)); } |
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.
Conclusion
This was a glimpse into Mockito. Stubbing would need more examples, because this is used heavily. I will do this in subsequent Blogs.
Keine Kommentare:
Kommentar veröffentlichen