Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Spies

A mock is an empty stand-in: every method returns an empty value until you stub it. A spy is the opposite: it wraps a real object, all methods work as usual, but Mockito records the calls and lets you replace individual methods. Spies are needed rarely — when you want to keep the real behavior and only check or change a small part of it.

A spy on a real repository

// src/test/java/com/example/blog/SpyTest.java
package com.example.blog;
 
import org.junit.jupiter.api.Test;
 
import java.util.Optional;
 
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
 
class SpyTest {
 
    @Test
    void spyCallsRealMethods() {
        InMemoryPostRepository repository = spy(new InMemoryPostRepository());
        PostService service = new PostService(repository, mock(Notifier.class));
 
        Post post = service.create("Hello", "anna");
 
        assertEquals(1L, post.id());
        assertEquals(1, repository.findAll().size());
        verify(repository).save(any(Post.class));
    }
}

spy(...) takes a ready object. The real save() assigns the id 1 and stores the post — findAll() really returns one post. At the same time verify works as with a mock.

Replacing one method

// src/test/java/com/example/blog/SpyTest.java
@Test
void spyCanOverrideOneMethod() {
    InMemoryPostRepository repository = spy(new InMemoryPostRepository());
    doReturn(Optional.of(new Post(7L, "From spy", "oleg", false)))
            .when(repository).findById(7L);
    PostService service = new PostService(repository, mock(Notifier.class));
 
    assertEquals("From spy", service.find(7L).title());
}

findById(7L) now returns the prepared post, all other methods remain real.

Mock or spy

MockSpy
Created fromA class or interfaceA ready object
Unstubbed methodsReturn empty valuesCall the real code
Stubbingwhen(...).thenReturn(...)doReturn(...).when(spy).method(...)
When to useAlmost alwaysLegacy code, checking calls on a real object

If you want a spy on the class under test itself — for example, to replace one of its own methods — it is usually a sign that the class does too much. Moving that part into a separate dependency and mocking it gives a cleaner design.

Common mistake

With a spy, when(spy.method()) first calls the real method — that is how Java evaluates the argument of when. If the real method throws, the stubbing never happens:

PostService service = spy(new PostService(new InMemoryPostRepository(), (to, message) -> { }));
 
when(service.find(7L)).thenReturn(new Post(7L, "From spy", "oleg", false));
[ERROR] com.example.blog.SpyWhenTest.spyWithWhen -- Time elapsed: 1.618 s <<< ERROR!
com.example.blog.PostNotFoundException: Post not found: 7
	at com.example.blog.PostService.find(PostService.java:24)
	at com.example.blog.SpyWhenTest.spyWithWhen(SpyWhenTest.java:16)

The real find(7L) looked into an empty repository and threw an exception. For spies always use the doReturn(...).when(spy).method(...) form: it does not call the real method.

Thanks for reading!

Donate to the site — we work hard for you.