Blog-Archiv

Sonntag, 6. Oktober 2019

Mockito for Java Unit Tests

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. using when(), 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: