Mockito 3 Spy

Mockito framework provides us with ways to mock classes and interface to create an exact replica of them so that the actual class and method will not be called, stubs to mock the required behavior for inputs and verify to validate the method calls and responses.

Spy is next level of partial mock.

We can still leverage the benefits of the verify method without actually mocking and stubbing the class or interface, for this, Mockito provides us spy method.

spies should be called for carefully, for example, while dealing with legacy code.

the spy method will call the actual class interface or methods and keep track of them for the verify method.

Mockito framework provides 2 ways to spy an object.

  • Mockito Spy method
  • @Spy Annotation

Mockito Spy Method

The org.mockito.Mockito.spy method helps us to create a spy object.

package com.example.mokito3.sujan;

public class AppleService {
    public String processApple(String appleName) {
        String message = "I love " + appleName + "Apple";
        System.out.println("message = " + message);
        return message;
    }
}
package com.example.mokito3.sujan;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;

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

@ExtendWith(MockitoExtension.class)
public class AppleServiceTest {

    @Test
    void saveAppleWithMockTest() {
        AppleService appleService = spy(AppleService.class);
        appleService.processApple("Macintosh");
        verify(appleService).processApple("Macintosh");
    }
}
plugins {
    id 'java'
}

group 'org.example'
version '1.0-SNAPSHOT'

repositories { jcenter() }
dependencies {
    testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
    testCompile 'org.mockito:mockito-junit-jupiter:3.4.4'
}
test {
    useJUnitPlatform()
}

@Spy Annotation

@Spy annotation also creates a spy object just like the static spy method but it is a cleaner way of doing it. It's a shorthand notation of spy creation. The same spy object can be used in multiple test cases hence it minimizes repetitive spy creation code which makes the test class more readable.

package com.example.mokito3.sujan;

public class AppleService {
    public String processApple(String appleName) {
        String message = "I love " + appleName + "Apple";
        System.out.println("message = " + message);
        return message;
    }
}
package com.example.mokito3.sujan;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.mockito.Mockito.verify;

@ExtendWith(MockitoExtension.class)
public class AppleServiceTest {
    @Spy
    private AppleService appleService;

    @Test
    void saveAppleWithMockTest() {
        appleService.processApple("Macintosh");
        verify(appleService).processApple("Macintosh");
    }
}
plugins {
    id 'java'
}

group 'org.example'
version '1.0-SNAPSHOT'

repositories { jcenter() }
dependencies {
    testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
    testCompile 'org.mockito:mockito-junit-jupiter:3.4.4'
}
test {
    useJUnitPlatform()
}

Spy with Stub

Mock object calls the stub method and the spy object calls the real method but the verify method can be used to validate both.

But spy object gives us the provision to call stub method as well. We can call the stub method as well as the original method from the spy object as per requirement.

The spy will call stub if found else will call real method whereas Mock will call stub if found or else will use Nice Mock and return null.

package com.example.mokito3.sujan;

public class AppleService {
    public String processApple(String appleName) {
        String message = "i love " + appleName + " Apple";
        System.out.println("message = " + message);
        return message;
    }

    public String saveApple(String appleName) {
        String message = "i like " + appleName + " Apple";
        System.out.println("message = " + message);
        return message;
    }
}
package com.example.mokito3.sujan;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;

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

@ExtendWith(MockitoExtension.class)
public class AppleServiceTest {
    @Spy
    private AppleService appleService;

    @Test
    void saveAppleWithMockTest() {
        when(appleService.processApple("Macintosh")).thenReturn("i eat apple");
        String appleMock = appleService.processApple("Macintosh");
        String apple = appleService.saveApple("Macintosh");
        verify(appleService).processApple("Macintosh");
        verify(appleService).saveApple("Macintosh");
        Assertions.assertEquals("i eat apple", appleMock);
        Assertions.assertEquals("i like Macintosh Apple", apple);
    }
}
plugins {
    id 'java'
}

group 'org.example'
version '1.0-SNAPSHOT'

repositories { jcenter() }
dependencies {
    testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
    testCompile 'org.mockito:mockito-junit-jupiter:3.4.4'
}
test {
    useJUnitPlatform()
}

Correct way to use spy

Mock stubs can be created using the mock method, Spy can also be created using that, but in some situation, mock method will fail.

So its always adviced to use doReturn | Answer | Throw() | CallRealMethod family of methods for stubbing.

package com.example.mokito3.sujan;

public class AppleService {
    public String processApple(String appleName) {
        String message = "i love " + appleName + " Apple";
        System.out.println("message = " + message);
        return message;
    }
}
package com.example.mokito3.sujan;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
public class AppleServiceTest {
    @Spy
    private AppleService appleService;

    @Test
    void saveAppleWithStaticMockTest() {
        AppleService appleService = mock(AppleService.class);
        doReturn("i eat apple").when(appleService).processApple("Macintosh");
        when(appleService.processApple("Fuji")).thenCallRealMethod();
        doThrow(new RuntimeException()).when(appleService).processApple(null);
        String appleMac = appleService.processApple("Macintosh");
        String appleFuji = appleService.processApple("Fuji");
        assertThrows(RuntimeException.class, () -> appleService.processApple(null));
        verify(appleService).processApple("Macintosh");
        verify(appleService).processApple("Fuji");
        verify(appleService).processApple(null);
        Assertions.assertEquals("i eat apple", appleMac);
        Assertions.assertEquals("i love Fuji Apple", appleFuji);
    }

    @Test
    void saveAppleWithAnnotationMockTest() {
        doReturn("i eat apple").when(appleService).processApple("Macintosh");
        when(appleService.processApple("Fuji")).thenCallRealMethod();
        doThrow(new RuntimeException()).when(appleService).processApple(null);
        String appleMac = appleService.processApple("Macintosh");
        String appleFuji = appleService.processApple("Fuji");
        assertThrows(RuntimeException.class, () -> appleService.processApple(null));
        verify(appleService).processApple("Macintosh");
        verify(appleService).processApple("Fuji");
        verify(appleService).processApple(null);
        Assertions.assertEquals("i eat apple", appleMac);
        Assertions.assertEquals("i love Fuji Apple", appleFuji);
    }
}
plugins {
    id 'java'
}

group 'org.example'
version '1.0-SNAPSHOT'

repositories { jcenter() }
dependencies {
    testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
    testCompile 'org.mockito:mockito-junit-jupiter:3.4.4'
}
test {
    useJUnitPlatform()
}

follow us on