How to use mocks

At this weekend I decided to refactor some parts of the circuit simulator, because its source slowly becomes confusing. In detail I began to extract the graph package of the simulator into a new module which can also be used in other projects relying on a graph library. While I was doing this I found out how I can use Mockito for improving my unit tests.

Mockito is a framework for mock creation. This means it wraps classes which your unit tests depend on into dummy classes returning some hardcoded values.

The advantage of this is, that you don’t need working implementations of interfaces which are dependencies of your unit tests but not being part of the functionality you want to test.

Here is an example of an unit test using mocks:

class DijkstraAlgorithmTest {
    private Graph<Integer> graph;
    private List<Node<Integer>> nodes;

    @BeforeEach
    void setUp() throws EdgeException {
        nodes = new LinkedList<>();
        for (int i = 0; i < 10; i++){
            Node<Integer> node = mock(Node.class);
            nodes.add(node);
            when(node.getData()).thenReturn(i);
            when(node.toString()).thenReturn(String.valueOf(i));
        }

        List<Edge<Integer>> edges = new ArrayList<>();
        Edge<Integer> edge;
        edge = mock(Edge.class); //0
        when(edge.getTarget(nodes.get(0))).thenReturn(nodes.get(1));
        when(edge.getTarget(nodes.get(1))).thenReturn(nodes.get(0));
        when(edge.getSource(nodes.get(1))).thenReturn(nodes.get(0));
        when(edge.getSource(nodes.get(0))).thenReturn(nodes.get(1));
        edges.add(edge);
        edge = mock(Edge.class); //1
        when(edge.getTarget(nodes.get(1))).thenReturn(nodes.get(4));
        when(edge.getTarget(nodes.get(4))).thenReturn(nodes.get(1));
        when(edge.getSource(nodes.get(4))).thenReturn(nodes.get(1));
        when(edge.getSource(nodes.get(1))).thenReturn(nodes.get(4));
        edges.add(edge);
        edge = mock(Edge.class); //2
        when(edge.getTarget(nodes.get(4))).thenReturn(nodes.get(6));
        when(edge.getTarget(nodes.get(6))).thenReturn(nodes.get(4));
        when(edge.getSource(nodes.get(6))).thenReturn(nodes.get(4));
        when(edge.getSource(nodes.get(4))).thenReturn(nodes.get(6));
        edges.add(edge);
        edge = mock(Edge.class); //3
        when(edge.getTarget(nodes.get(0))).thenReturn(nodes.get(6));
        when(edge.getTarget(nodes.get(6))).thenReturn(nodes.get(0));
        when(edge.getSource(nodes.get(6))).thenReturn(nodes.get(0));
        when(edge.getSource(nodes.get(0))).thenReturn(nodes.get(6));
        edges.add(edge);
        edge = mock(Edge.class); //4
        when(edge.getTarget(nodes.get(6))).thenReturn(nodes.get(2));
        when(edge.getTarget(nodes.get(2))).thenReturn(nodes.get(6));
        when(edge.getSource(nodes.get(2))).thenReturn(nodes.get(6));
        when(edge.getSource(nodes.get(6))).thenReturn(nodes.get(2));
        edges.add(edge);
        edge = mock(Edge.class); //5
        when(edge.getTarget(nodes.get(2))).thenReturn(nodes.get(1));
        when(edge.getTarget(nodes.get(1))).thenReturn(nodes.get(2));
        when(edge.getSource(nodes.get(1))).thenReturn(nodes.get(2));
        when(edge.getSource(nodes.get(2))).thenReturn(nodes.get(1));
        edges.add(edge);
        edge = mock(Edge.class); //6
        when(edge.getTarget(nodes.get(2))).thenReturn(nodes.get(3));
        when(edge.getTarget(nodes.get(3))).thenReturn(nodes.get(2));
        when(edge.getSource(nodes.get(3))).thenReturn(nodes.get(2));
        when(edge.getSource(nodes.get(2))).thenReturn(nodes.get(3));
        edges.add(edge);
        edge = mock(Edge.class); //7
        when(edge.getTarget(nodes.get(1))).thenReturn(nodes.get(3));
        when(edge.getTarget(nodes.get(3))).thenReturn(nodes.get(1));
        when(edge.getSource(nodes.get(3))).thenReturn(nodes.get(1));
        when(edge.getSource(nodes.get(1))).thenReturn(nodes.get(3));
        edges.add(edge);

        graph = mock(Graph.class);
        when(graph.getAllNodes()).thenReturn(new HashSet<>(nodes));
        when(graph.getAllEdges()).thenReturn(new HashSet<>(edges));
        when(graph.getEdges(nodes.get(0))).thenReturn(new HashSet<>(Arrays.asList(
                edges.get(0),
                edges.get(3)
        )));
        when(graph.getEdges(nodes.get(1))).thenReturn(new HashSet<>(Arrays.asList(
                edges.get(0),
                edges.get(1),
                edges.get(5),
                edges.get(7)
        )));
        when(graph.getEdges(nodes.get(2))).thenReturn(new HashSet<>(Arrays.asList(
                edges.get(4),
                edges.get(5),
                edges.get(6)
        )));
        when(graph.getEdges(nodes.get(3))).thenReturn(new HashSet<>(Arrays.asList(
                edges.get(6),
                edges.get(7)
        )));
        when(graph.getEdges(nodes.get(4))).thenReturn(new HashSet<>(Arrays.asList(
                edges.get(1),
                edges.get(2)
        )));
        when(graph.getEdges(nodes.get(6))).thenReturn(new HashSet<>(Arrays.asList(
                edges.get(2),
                edges.get(3),
                edges.get(4)
        )));
    }

    @Test
    void apply1() {
        assertEquals(new Path<>(Arrays.asList(
                nodes.get(0),
                nodes.get(1),
                nodes.get(3)
        )), new DijkstraAlgorithm<Integer>().apply(graph, new DijkstraAlgorithm.Parameters<>(nodes.get(0), nodes.get(3))));
    }

    @Test
    void apply2() {
        assertEquals(new Path<>(Arrays.asList(
                nodes.get(4),
                nodes.get(1),
                nodes.get(3)
        )), new DijkstraAlgorithm<Integer>().apply(graph, new DijkstraAlgorithm.Parameters<>(nodes.get(4), nodes.get(3))));
    }
}

 

Let’s take a more detailed look into what happens in this piece of code. First a list of mocked objects of type Node is created. Node is an interface representing nodes in a graph.

nodes = new LinkedList<>();
        for (int i = 0; i < 10; i++){
            Node<Integer> node = mock(Node.class);
            nodes.add(node);
            when(node.getData()).thenReturn(i);
            when(node.toString()).thenReturn(String.valueOf(i));
        }

The mock(Node.class) instruction creates a new instance of an anonymous class implementing the interface Node.At this point, all methods of the created instance will return null. To be able to work with that instance, it is necessary to specify for each function the unit test will use what it should do.

This is done by the when(…).then(…) instructions:

when(node.getData()).thenReturn(i);

This specifies, that the value of the loop counter i shall be returned when the function getData() of the Node instance is called.

when(node.toString()).thenReturn(String.valueOf(i));

I included this for debugging purposes. It returns the loop counter i formatted into a String when the toString() method of the instance is called. The unit test itself does not use this function, but it make debugging easier because I better can see which node I have when looking at the variables stack of my IDE.

After this it is necessary to mock some edge instances for the graph to be tested:

List<Edge<Integer>> edges = new ArrayList<>();
        Edge<Integer> edge;
        edge = mock(Edge.class); //0
        when(edge.getTarget(nodes.get(0))).thenReturn(nodes.get(1));
        when(edge.getTarget(nodes.get(1))).thenReturn(nodes.get(0));
        when(edge.getSource(nodes.get(1))).thenReturn(nodes.get(0));
        when(edge.getSource(nodes.get(0))).thenReturn(nodes.get(1));
        edges.add(edge);
        ...

Create a new mocked instance of an Edge by calling mock(Edge.class). Now specify the getTarget(…) and getSource(…) functions of the Edge:

when(edge.getTarget(nodes.get(0))).thenReturn(nodes.get(1));

This specifies that the node at position 1 of the list nodes shall be returned when getTarget(…) is called for the Edge with the node at position 0 of the list nodes.

when(edge.getSource(nodes.get(1))).thenReturn(nodes.get(0));

This specifies that the node at position 0 of the list nodes is being returned when calling getSource(…) for the Edge with the node at position 1 of the list nodes.

The same way the a mocked instance of Graph is created. Then the actual test is created:

@Test
    void apply1() {
        assertEquals(new Path<>(Arrays.asList(
                nodes.get(0),
                nodes.get(1),
                nodes.get(3)
        )), new DijkstraAlgorithm<Integer>().apply(graph, new DijkstraAlgorithm.Parameters<>(nodes.get(0), nodes.get(3))));
    }

As you can see, you now can use the mocked instances like normal implementations of Graph, Edge and Node.