Mockito Spy Example
In this article, I am going to show you an example of Mockito Spy calls.
There are times when we would like to use most of the original object’s behavior but mock only a portion of it. This is called spying objects, also called partial mocking. Before I start with the example, let me first brief you about my setup:
- I am using Maven – the build tool
- Eclipse as the IDE, version Luna 4.4.1.
- TestNG is my testing framework, in case you are new to TestNG, please refer TestNG Maven Project Example.
- Add Mockito dependency to our
pom.xml
.
1. Example of Mockito Spy
Using Mockito’s spy feature, we can mock only those methods of a real object that we want to, thus retaining the rest of the original behavior.
The system under test is an Employee
bean which takes in firstName
, lastName
and age
. It has the getter methods for all the attributes. It also has an additional getter method getFullName()
which returns us firstName
and lastName
together. Internally, it relies on the respective getter methods rather than accessing attributes directly. Finally, it also has a setter method on age
attribute. Why a setter method on the age
attribute? The answer is, purely to prove a point regarding how the spy works and there is no design secret behind it.
In the below example, I will show you how to set the expected behavior on a couple of Employee
bean methods.
Employee:
package com.javacodegeeks.mockito; import java.util.ArrayList; import java.util.List; public class Employee { private String firstName; private String lastName; private int age; public Employee(String firstName, String lastName, int age) { this.firstName = firstName; this.lastName = lastName; this.age = age; this.engineerAware = new Dev(); } public int getAge() { return age; } public String getFullName() { return getFirstName() + " " + getLastName(); } public String getLastName() { return lastName; } public String getFirstName() { return firstName; } public void setAge(int age) { this.age = age; } }
Let’s analyze the test cases now.
We create the spy object using org.mockito.Mockito.spy(real object)
. In our example, we do it in the @BeforeMethod
, buildSpy()
. We create the Employee
bean and then the spy object using spy(emp)
.
Since we have two Employee
beans, one the original and the other spy, one question that naturally arises is, whether the spy object refers to the original object internally. The answer is No. Mockito creates a copy of the original object, so when methods are exercised on the spy object, the state of the original object remains unaffected. Likewise, if you interact with the real object, the object won’t be aware of those interactions.
This is proved in verifySpyEffectOnRealInstance()
where we set the age on the spied Employee
bean, but the state of the real Employee
bean still retains the original age. In the next two test cases, we will analyze the verification and partial mocking.
MockitoSpyExample:
package com.javacodegeeks.mockito; import static org.mockito.Mockito.*; import static org.testng.Assert.*; import org.mockito.InOrder; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class MockitoSpyExample { private Employee spyEmp; private Employee emp; private static final String FIRST_NAME = "Joe"; private static final String LAST_NAME = "M"; private static final int AGE = 35; @BeforeMethod public void buildSpy() { emp = new Employee(FIRST_NAME, LAST_NAME, AGE); spyEmp = spy(emp); } @Test public void verifySpyEffectOnRealInstance() { spyEmp.setAge(20); assertFalse(emp.getAge() == spyEmp.getAge()); } @Test public void verifyEmployeeDetails() { System.out.println("Full name:" + spyEmp.getFullName()); System.out.println("Age:" + spyEmp.getAge()); InOrder inOrder = inOrder(spyEmp); System.out.println("Verify emp.getFullName() calls getFirstName() and then getLastName()"); inOrder.verify(spyEmp).getFirstName(); inOrder.verify(spyEmp).getLastName(); System.out.println("Verify emp.getAge() is called"); verify(spyEmp).getAge(); assertEquals(spyEmp.getFirstName(), FIRST_NAME); assertEquals(spyEmp.getLastName(), LAST_NAME); assertEquals(spyEmp.getFullName(), FIRST_NAME + " " + LAST_NAME); assertEquals(spyEmp.getAge(), AGE); System.out.println("Verify emp.getFullName() called twice"); verify(spyEmp, times(2)).getFullName(); } @Test public void spyEmployeeName() { final String I_AM = "I am"; final String THE_SPY = "the Spy"; System.out.println("Train employee to return " + I_AM + " when emp.getFirstName() is called"); when(spyEmp.getFirstName()).thenReturn(I_AM); System.out.println("Full Name: " + spyEmp.getFullName()); assertEquals(I_AM + " M", spyEmp.getFullName()); System.out.println("Train employee to return " + THE_SPY + " when emp.getLastName() is called"); when(spyEmp.getLastName()).thenReturn(THE_SPY); System.out.println("Full Name: " + spyEmp.getFullName()); assertEquals(I_AM + " " + THE_SPY, spyEmp.getFullName()); } }
In verifyEmployeeDetails()
, we call getFullName()
on the spied object. We know internally it calls getFirstName()
and getLastName()
. We can verify this and the order in which they are called using InOrder
. The spy object is encapsulated in InOrder
object and then we verify the order of interaction of getFirstName()
and getLastName().
We can also verify the method invocations by their count. For example, verify(spyEmp, times(2)).getFullName()
.
In spyEmployeeName()
, we do the partial mocking. We train the spied Employee
bean to return “I am” when getFirstName()
is called. Since getFullName()
calls getFirstName()
and getLastName()
, we can see the change in its full name when getFullName()
is called. It returns “I am M”.
Next, we train the getLastName()
to return the spy. This again reflects in the full name and returns “I am the spy”.
Output:
PASSED: verifySpyEffectOnRealInstance Train employee to return I am when emp.getFirstName() is called Full Name: I am M Train employee to return the Spy when emp.getLastName() is called Full Name: I am the Spy PASSED: spyEmployeeName Full name:Joe M Age:35 Verify emp.getFullName() calls getFirstName() and then getLastName() Verify emp.getAge() is called Verify emp.getFullName() called twice PASSED: verifyEmployeeDetails
2. Spying on Interface
In the below example, I show you that one can even spy on an anonymous object. In the test case spyOnInterface
, we create a spy object on EngineerAware
implementation called Dev
. We can see it returns us the expected enum Engineer.DEV
. Next, we train the spy object to return Engineer.QA,
which it does.
EngineerAware:
package com.javacodegeeks.mockito; public interface EngineerAware { Engineer getDesignation(); enum Engineer { DEV,QA } }
SpyOnInterfaceExample:
package com.javacodegeeks.mockito; import org.testng.annotations.Test; import com.javacodegeeks.mockito.EngineerAware.Engineer; import static org.mockito.Mockito.*; import static org.testng.Assert.*; public class SpyOnInterfaceExample { @Test public void spyOnInterface() { EngineerAware engineerAware = spy(new Dev()); assertEquals(Engineer.DEV, engineerAware.getDesignation()); when(engineerAware.getDesignation()).thenReturn(Engineer.QA); assertEquals(Engineer.QA, engineerAware.getDesignation()); } private class Dev implements EngineerAware { @Override public Engineer getDesignation() { return Engineer.DEV; } } }
3. Stubbing a final method in a Spy Object
In this Mockito spy example, I demonstrate that we can’t train a final method. Method moveTo()
, updates employee’s designation. We also have another method, finalMoveTo()
which does the same as moveTo ()
but is a final method.
In our test cases, we will try to train the final method and see how it behaves.
Employee:
package com.javacodegeeks.mockito; import java.util.ArrayList; import java.util.List; public class Employee { private String firstName; private String lastName; private int age; private EngineerAware engineerAware; private List skills; public Employee(String firstName, String lastName, int age) { this.firstName = firstName; this.lastName = lastName; this.age = age; this.engineerAware = new Dev(); } public int getAge() { return age; } public String getFullName() { return getFirstName() + " " + getLastName(); } public String getLastName() { return lastName; } public String getFirstName() { return firstName; } public void setAge(int age) { this.age = age; } public final void finalMoveTo(EngineerAware engineerAware) { System.out.println("Employee moves from " + this.engineerAware.getDesignation() + " to " + engineerAware.getDesignation()); this.engineerAware = engineerAware; } public void moveTo(EngineerAware engineerAware) { System.out.println("Employee moves from " + this.engineerAware.getDesignation() + " to " + engineerAware.getDesignation()); this.engineerAware = engineerAware; } private class Dev implements EngineerAware { @Override public Engineer getDesignation() { return Engineer.DEV; } } }
- In the test case,
stubNonFinalMoveTo()
, we trainmoveTo()
method to throw aRuntimeException
. - In
stubFinalMoveTo()
, we do the same with the final methodfinalMoveTo()
.
Since finalMoveTo()
is final, Mockito fails to train it and instead simply invokes the real object’s method, which is why it fails to throw RuntimeException
.
StubOnFinalMethod:
package com.javacodegeeks.mockito; import static org.mockito.Mockito.*; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class StubOnFinalMethod { private Employee emp; private static final String FIRST_NAME = "Joe"; private static final String LAST_NAME = "M"; private static final int AGE = 35; QA qa = new QA(); @BeforeMethod public void buildSpy() { emp = spy(new Employee(FIRST_NAME, LAST_NAME, AGE)); } @Test(expectedExceptions=RuntimeException.class) public void stubNonFinalMoveTo() { doThrow(new RuntimeException("Can't move to a different department")).when(emp).moveTo(qa); emp.moveTo(qa); } @Test(expectedExceptions=RuntimeException.class) public void stubFinalMoveTo() { doThrow(new RuntimeException("Can't move to a different department")).when(emp).finalMoveTo(qa); emp.finalMoveTo(qa); } private class QA implements EngineerAware { @Override public Engineer getDesignation() { return Engineer.QA; } } }
Output:
Employee moves from DEV to QA Employee moves from QA to QA FAILED: stubFinalMoveTo org.testng.TestException: Expected exception java.lang.RuntimeException but got org.testng.TestException: PASSED: stubNonFinalMoveTo
4. Using doReturn(Object) for stubbing spies
By using Mockito Spy, there are times when calling when(Object) for stubbing might be inappropriate. In such situations, we should consider using doReturn
for stubbing.
In our Employee
bean, I have added a couple of new methods to help us capture three employee skills. We access the skill using getSkill(index)
which returns us the skill based on the index passed in.
Employee:
package com.javacodegeeks.mockito; import java.util.ArrayList; import java.util.List; public class Employee { private String firstName; private String lastName; private int age; private EngineerAware engineerAware; private List skills; public Employee(String firstName, String lastName, int age) { this.firstName = firstName; this.lastName = lastName; this.age = age; this.engineerAware = new Dev(); } public int getAge() { return age; } public String getFullName() { return getFirstName() + " " + getLastName(); } public String getLastName() { return lastName; } public String getFirstName() { return firstName; } public void setAge(int age) { this.age = age; } public final void finalMoveTo(EngineerAware engineerAware) { System.out.println("Employee moves from " + this.engineerAware.getDesignation() + " to " + engineerAware.getDesignation()); this.engineerAware = engineerAware; } public void moveTo(EngineerAware engineerAware) { System.out.println("Employee moves from " + this.engineerAware.getDesignation() + " to " + engineerAware.getDesignation()); this.engineerAware = engineerAware; } private class Dev implements EngineerAware { @Override public Engineer getDesignation() { return Engineer.DEV; } } public String getSkill(int index) { return skills.get(index); } public void addSkill(String skill1, String skill2, String skill3) { if (skills == null) { skills = new ArrayList(3); } skills.add(0, skill1); skills.add(1, skill2); skills.add(2, skill3); } }
Suppose we want to train our spy object Employee
bean to return us “SPY” when getSkill(0)
is called, using when()
API like below will throw NullPointerException
. Note that spyEmp.getSkill(0)
calls on the original method and since the List
object is not yet initialized, it throws NullPointerException
.
when(spyEmp.getSkill(0)).thenReturn("SPY");
This can be done differently using doReturn()
for stubbing. For example, in the below style, we get around the NullPointerException
.
doReturn("SPY").when(spyEmp).getSkill(0);
Both the cases are demonstrated in spySkillUsingWhenThenReturn
and spySkillUsingDoWhen
.
MockitoSpyExample:
package com.javacodegeeks.mockito; import static org.mockito.Mockito.*; import static org.testng.Assert.*; import org.mockito.InOrder; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class MockitoSpyExample { private Employee spyEmp; private Employee emp; private static final String FIRST_NAME = "Joe"; private static final String LAST_NAME = "M"; private static final int AGE = 35; @BeforeMethod public void buildSpy() { emp = new Employee(FIRST_NAME, LAST_NAME, AGE); spyEmp = spy(emp); } @Test public void verifySpyEffectOnRealInstance() { spyEmp.setAge(20); assertFalse(emp.getAge() == spyEmp.getAge()); } @Test public void verifyEmployeeDetails() { System.out.println("Full name:" + spyEmp.getFullName()); System.out.println("Age:" + spyEmp.getAge()); InOrder inOrder = inOrder(spyEmp); System.out.println("Verify emp.getFullName() calls getFirstName() and then getLastName()"); inOrder.verify(spyEmp).getFirstName(); inOrder.verify(spyEmp).getLastName(); System.out.println("Verify emp.getAge() is called"); verify(spyEmp).getAge(); assertEquals(spyEmp.getFirstName(), FIRST_NAME); assertEquals(spyEmp.getLastName(), LAST_NAME); assertEquals(spyEmp.getFullName(), FIRST_NAME + " " + LAST_NAME); assertEquals(spyEmp.getAge(), AGE); System.out.println("Verify emp.getFullName() called twice"); verify(spyEmp, times(2)).getFullName(); } @Test public void spyEmployeeName() { final String I_AM = "I am"; final String THE_SPY = "the Spy"; System.out.println("Train employee to return " + I_AM + " when emp.getFirstName() is called"); when(spyEmp.getFirstName()).thenReturn(I_AM); System.out.println("Full Name: " + spyEmp.getFullName()); assertEquals(I_AM + " M", spyEmp.getFullName()); System.out.println("Train employee to return " + THE_SPY + " when emp.getLastName() is called"); when(spyEmp.getLastName()).thenReturn(THE_SPY); System.out.println("Full Name: " + spyEmp.getFullName()); assertEquals(I_AM + " " + THE_SPY, spyEmp.getFullName()); } @Test public void spySkillUsingWhenThenReturn() { when(spyEmp.getSkill(0)).thenReturn("SPY"); assertEquals("SPY", spyEmp.getSkill(0)); } @Test public void spySkillUsingDoWhen() { doReturn("SPY").when(spyEmp).getSkill(0); assertEquals("SPY", spyEmp.getSkill(0)); } }
Output:
FAILED: spySkillUsingWhenThenReturn java.lang.NullPointerException at com.javacodegeeks.mockito.Employee.getSkill(Employee.java:54) PASSED: spySkillUsingDoWhen
5. Download the Eclipse Project
This was an example of Mockito Spy.
You can download the full source code of this example here: mockitoSpy.zip
I think your example 1 output is not in the right order.