Mockito Spy Real Objects

0

At times we may want to stub the behavior of a real object, especially, when you are working with some legacy code and your in the middle of re-factoring. You may also want to stub out some behavior if the real object you are working with is dependent on some third party interface.

You cannot mock a real object but still you need something that allows you to stub certain behavior while still allowing to use the original behavior for the other methods that are not stubbed.

You do this using something called spying on a real object. Once an expectation is set for a method, on a spy object, then the spy no longer returns the original value. It starts returning the stubbed value.

Using Mockito, we can create spy of real objects using spy(real object).

Below are my setup details:

Dependencies

pom.xml:

<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>com.javarticles.mockito</groupId>
  <artifactId>mockitoVerifyBehavior</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
	<dependencies>
		<dependency>
			<groupId>org.testng</groupId>
			<artifactId>testng</artifactId>
			<version>6.8.8</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.mockito</groupId>
			<artifactId>mockito-core</artifactId>
			<version>2.0.5-beta</version>
		</dependency>
	</dependencies>
</project>

Create Spy

You create a spy using Mockito.spy(), pass the object you want to spy on.

In the below example, we create a spy on an ArrayList object.

MockitoSpyingOnRealObjectsTests:

package com.javarticles.mockito;

import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.List;

import org.testng.annotations.BeforeMethod;
import org.testng.annotations.<a class="zem_slink" href="http://en.wikipedia.org/wiki/Test_cricket" title="Test cricket" rel="wikipedia" target="_blank">Test</a>;

public class MockitoSpyingOnRealObjectsTests {
    private List<integer> list;
    private List<integer> spyList;

    @BeforeMethod
    public void createSpy() {
        list = new ArrayList<integer>();
        spyList = spy(list);
    }
}

Stubbing Spies

We may be tempted to use the when(spy).thenReturn pattern but this may not work in case of spies as the spy object is holding a real object.

Below statement will throw IndexOutOfBoundsException as the wrapped ArrayList object doesn’t have any elements yet.

when(spyList.get(0)).thenReturn(3); 

MockitoSpyingOnRealObjectsTests:

package com.javarticles.mockito;

import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.List;

import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

public class MockitoSpyingOnRealObjectsTests {
    private List<Integer> list;
    private List<Integer> spyList;

    @BeforeMethod
    public void createSpy() {
        list = new ArrayList<Integer>();
        spyList = spy(list);
    }
               
    @Test(expectedExceptions=IndexOutOfBoundsException.class)
    public void stubbingSpyObjectThrowsException() {
        when(spyList.get(0)).thenReturn(3);        
    }
}

To order to avoid this kind of exceptions, we will have to use doReturn().when(spy).method() pattern.
The proper way to stub is:

doReturn(3).when(spyList).get(0);

We can also verify that the method was invoked using:

verify(spyList).get(0);

MockitoSpyingOnRealObjectsTests:

package com.javarticles.mockito;

import static org.mockito.Mockito.*;

import java.util.ArrayList;
import java.util.List;

import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import static org.testng.Assert.*;

public class MockitoSpyingOnRealObjectsTests {
    private List<Integer> list;
    private List<Integer> spyList;

    @BeforeMethod
    public void createSpy() {
        list = new ArrayList<Integer>();
        spyList = spy(list);
    }
               
    @Test(expectedExceptions=IndexOutOfBoundsException.class)
    public void stubbingSpyObjectThrowsException() {
        when(spyList.get(0)).thenReturn(3);        
    }
    
    @Test
    public void stubbingSpyObject() {
        doReturn(3).when(spyList).get(0);
        assertEquals(spyList.get(0), new Integer(3));
        verify(spyList).get(0);
    }
}

Stub Consecutive times on Spy

In stubbingSpyConsecutively(), we stub multiple times. Since the list contains one element, we can also use the when(spy).thenReturn style.

when(spyList.get(0)).thenReturn(3).thenReturn(2);

MockitoSpyingOnRealObjectsTests:

package com.javarticles.mockito;

import static org.mockito.Mockito.*;

import java.util.ArrayList;
import java.util.List;

import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import static org.testng.Assert.*;

public class MockitoSpyingOnRealObjectsTests {
    private List<Integer> list;
    private List<Integer> spyList;

    @BeforeMethod
    public void createSpy() {
        list = new ArrayList<Integer>();
        spyList = spy(list);
    }

    @Test(expectedExceptions = IndexOutOfBoundsException.class)
    public void stubbingSpyObjectThrowsException() {
        when(spyList.get(0)).thenReturn(3);
    }

    @Test
    public void stubbingSpyObject() {
        doReturn(3).when(spyList).get(0);
        assertEquals(spyList.get(0), new Integer(3));
        verify(spyList).get(0);
    }

    @Test
    public void stubbingSpyConsecutively() {
        spyList.add(4);
        doReturn(3).doReturn(2).when(spyList).get(0);
        assertEquals(spyList.get(0), new Integer(3));
        assertEquals(spyList.get(0), new Integer(2));
        assertEquals(spyList.get(0), new Integer(2));
        
        when(spyList.get(0)).thenReturn(3).thenReturn(2);
        assertEquals(spyList.get(0), new Integer(3));
        assertEquals(spyList.get(0), new Integer(2));
        assertEquals(spyList.get(0), new Integer(2));
    }
}

Spy works on a copy of real Object

Mockito.spy() works on a copy of real instance. When an unstubbed method is called on the spy, it will be working the copied instance so it will not have any state effects on the real object.

MockitoSpyingOnRealObjectsTests:

package com.javarticles.mockito;

import static org.mockito.Mockito.*;

import java.util.ArrayList;
import java.util.List;

import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import static org.testng.Assert.*;

public class MockitoSpyingOnRealObjectsTests {
    private List<Integer> list;
    private List<Integer> spyList;

    @BeforeMethod
    public void createSpy() {
        list = new ArrayList<Integer>();
        spyList = spy(list);
    }

    @Test(expectedExceptions = IndexOutOfBoundsException.class)
    public void stubbingSpyObjectThrowsException() {
        when(spyList.get(0)).thenReturn(3);
    }

    @Test
    public void stubbingSpyObject() {
        doReturn(3).when(spyList).get(0);
        assertEquals(spyList.get(0), new Integer(3));
        verify(spyList).get(0);
    }

    @Test
    public void stubbingSpyConsecutively() {
        spyList.add(4);
        doReturn(3).doReturn(2).when(spyList).get(0);
        assertEquals(spyList.get(0), new Integer(3));
        assertEquals(spyList.get(0), new Integer(2));
        assertEquals(spyList.get(0), new Integer(2));
        
        when(spyList.get(0)).thenReturn(3).thenReturn(2);
        assertEquals(spyList.get(0), new Integer(3));
        assertEquals(spyList.get(0), new Integer(2));
        assertEquals(spyList.get(0), new Integer(2));
    }
    
    @Test
    public void spyIsBuiltFromACopyOfRealObject() {
        spyList.add(3);
        assertTrue(list.isEmpty());
        list.add(1);
        assertEquals(spyList.get(0), new Integer(3));
    }
}

Spy on Anonymous Object

You can also spy on anonymous object.

Below is a simple interface which we will used to create an anonymous object.

Tree:

package com.javarticles.mokcito;

public interface Tree {
    Seed createSeed();
    
    String getName();
    
    void pluckFruits();
}

Seed:

package com.javarticles.mokcito;

public class Seed {
    private String name;
    
    public Seed(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
    
    public String toString() {
        return "Seed(" + name + ")";
    }
}

tree.getName() returns ‘Apple Tree’. We will stub it to return ‘Green Apples’. Next, we will also stub spyTree.createSeed() to throw a RuntimeException.
Method spyTree.pluckFruits() is unstubbed and will exhibit the real object’s behavior.

MockitoSpyingOnRealObjectsTests:

package com.javarticles.mockito;

import static org.mockito.Mockito.*;

import java.util.ArrayList;
import java.util.List;

import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import com.javarticles.mokcito.Seed;
import com.javarticles.mokcito.Tree;

import static org.testng.Assert.*;

public class MockitoSpyingOnRealObjectsTests {
    private List<Integer> list;
    private List<Integer> spyList;

    @BeforeMethod
    public void createSpy() {
        list = new ArrayList<Integer>();
        spyList = spy(list);
    }

    @Test(expectedExceptions = IndexOutOfBoundsException.class)
    public void stubbingSpyObjectThrowsException() {
        when(spyList.get(0)).thenReturn(3);
    }

    @Test
    public void stubbingSpyObject() {
        doReturn(3).when(spyList).get(0);
        assertEquals(spyList.get(0), new Integer(3));
        verify(spyList).get(0);
    }

    @Test
    public void stubbingSpyConsecutively() {
        spyList.add(4);
        doReturn(3).doReturn(2).when(spyList).get(0);
        assertEquals(spyList.get(0), new Integer(3));
        assertEquals(spyList.get(0), new Integer(2));
        assertEquals(spyList.get(0), new Integer(2));
        
        when(spyList.get(0)).thenReturn(3).thenReturn(2);
        assertEquals(spyList.get(0), new Integer(3));
        assertEquals(spyList.get(0), new Integer(2));
        assertEquals(spyList.get(0), new Integer(2));
    }
    
    @Test
    public void spyIsBuiltFromACopyOfRealObject() {
        spyList.add(3);
        assertTrue(list.isEmpty());
        list.add(1);
        assertEquals(spyList.get(0), new Integer(3));
    }
    
    @Test(expectedExceptions=RuntimeException.class)
    public void spyOnAnonymousObject() {
        Tree spyTree = spy(new Tree(){

            public Seed createSeed() {
                return new Seed(getName());
            }

            public String getName() {
                return "Apple Tree";
            }

            public void pluckFruits() {
                System.out.println("Pluck " + getName());
            }});
        when(spyTree.getName()).thenReturn("Green Apples");
        when(spyTree.createSeed()).thenThrow(new RuntimeException("Can't create any seeds!"));
        assertEquals("Green Apples", spyTree.getName());
        spyTree.pluckFruits();
        spyTree.createSeed();
        verify(spyTree, times(2)).getName();
        verify(spyTree).pluckFruits();
        verify(spyTree).createSeed();
        verifyNoMoreInteractions(spyTree);
    }
}

Download source code

This was an example about Mockito Spy.

You can download the source code here: mockitoSpyingOnRealObjects.zip
Share.

Comments are closed.