Mockito’s capture and answer features let you introspect and manipulate behavior during tests, going beyond simple argument matching.

Let’s see it in action. Imagine you have a service that sends notifications, and you want to test that it correctly formats and sends them.

public class NotificationService {
    private final NotificationSender sender;

    public NotificationService(NotificationSender sender) {
        this.sender = sender;
    }

    public void sendWelcomeEmail(User user) {
        String subject = "Welcome, " + user.getName() + "!";
        String body = "Hi " + user.getName() + ",\n\nWelcome aboard!";
        sender.sendEmail(user.getEmail(), subject, body);
    }
}

public interface NotificationSender {
    void sendEmail(String recipient, String subject, String body);
}

Here’s a test using ArgumentCaptor to verify the exact arguments passed to sendEmail:

import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import static org.mockito.Mockito.*;

public class NotificationServiceTest {

    @Test
    void sendWelcomeEmail_capturesCorrectArguments() {
        NotificationSender mockSender = mock(NotificationSender.class);
        NotificationService service = new NotificationService(mockSender);
        User testUser = new User("Alice", "alice@example.com");

        service.sendWelcomeEmail(testUser);

        ArgumentCaptor<String> recipientCaptor = ArgumentCaptor.forClass(String.class);
        ArgumentCaptor<String> subjectCaptor = ArgumentCaptor.forClass(String.class);
        ArgumentCaptor<String> bodyCaptor = ArgumentCaptor.forClass(String.class);

        verify(mockSender).sendEmail(recipientCaptor.capture(), subjectCaptor.capture(), bodyCaptor.capture());

        assertEquals("alice@example.com", recipientCaptor.getValue());
        assertEquals("Welcome, Alice!", subjectCaptor.getValue());
        assertEquals("Hi Alice,\n\nWelcome aboard!", bodyCaptor.getValue());
    }
}

The ArgumentCaptor is the key here. You declare captors for the types of arguments you expect. When you verify the mock, you pass these captors. After verify, captor.getValue() retrieves the last value passed to that argument during the verified method call. This is incredibly useful when the exact content of arguments matters, especially for complex objects or strings that are dynamically generated.

But what if you need to change the behavior of a mock based on the arguments it receives, or if you want to test a method that returns a value based on input? That’s where Answer comes in.

Consider a scenario where your NotificationSender has a rate limiter, and you want to simulate a scenario where sending an email sometimes fails.

import org.junit.jupiter.api.Test;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(MockitoExtension.class)
public class NotificationServiceWithFailingSendTest {

    @Mock
    private NotificationSender mockSender;
    private NotificationService service;

    @BeforeEach
    void setUp() {
        service = new NotificationService(mockSender);
    }

    @Test
    void sendWelcomeEmail_handlesSenderFailure() {
        // Simulate the sender sometimes throwing an exception
        doThrow(new RuntimeException("Rate limit exceeded"))
            .when(mockSender)
            .sendEmail(anyString(), anyString(), anyString());

        User testUser = new User("Bob", "bob@example.com");

        // We expect this to throw an exception, and we want to verify
        // that the sender was indeed called before the exception occurred.
        // This is where ArgumentCaptor is still useful.
        ArgumentCaptor<String> recipientCaptor = ArgumentCaptor.forClass(String.class);

        // We wrap the call in assertThrows to catch the expected exception
        assertThrows(RuntimeException.class, () -> {
            service.sendWelcomeEmail(testUser);
        });

        // Verify that sendEmail was attempted with the correct recipient
        verify(mockSender).sendEmail(recipientCaptor.capture(), anyString(), anyString());
        assertEquals("bob@example.com", recipientCaptor.getValue());
    }
}

In this example, doThrow is a specific type of Answer that makes the mock throw an exception. Mockito’s Answer interface is more general. You can implement Answer to define custom behavior for a mocked method.

import org.junit.jupiter.api.Test;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

@ExtendWith(MockitoExtension.class)
public class NotificationServiceWithCustomAnswerTest {

    @Mock
    private NotificationSender mockSender;
    private NotificationService service;

    @BeforeEach
    void setUp() {
        service = new NotificationService(mockSender);
    }

    @Test
    void sendWelcomeEmail_usesCustomAnswerForResponse() {
        // Use a custom Answer to simulate a delayed send and return a transaction ID
        doAnswer(new Answer<Void>() { // Note: Void because sendEmail returns void
            @Override
            public Void answer(InvocationOnMock invocation) throws Throwable {
                // You can access arguments here if needed:
                String recipient = invocation.getArgument(0);
                String subject = invocation.getArgument(1);
                String body = invocation.getArgument(2);

                System.out.println("Simulating sending email to: " + recipient + " with subject: " + subject);
                // In a real scenario, you might add a Thread.sleep(100); here

                // Since sendEmail returns void, we return null.
                // If it returned a value, you'd return that value.
                return null;
            }
        }).when(mockSender).sendEmail(anyString(), anyString(), anyString());

        User testUser = new User("Charlie", "charlie@example.com");
        service.sendWelcomeEmail(testUser);

        // Verify that the answer was executed (e.g., by checking logs or side effects)
        // For this example, we'll just verify the call happened.
        verify(mockSender).sendEmail(eq("charlie@example.com"), contains("Welcome, Charlie!"), contains("Hi Charlie"));
    }
}

The Answer interface allows you to execute arbitrary Java code when a mocked method is called. The InvocationOnMock object provides access to the method being called, its arguments, and the mock object itself. This is powerful for simulating complex return values, side effects, or even conditional logic within your mocks.

Finally, there’s spy. A spy allows you to wrap a real object and selectively mock its methods. Unlike a mock, a spy calls the real implementation of methods unless you explicitly stub them.

import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;

public class NotificationServiceWithSpyTest {

    @Test
    void sendWelcomeEmail_usesRealObjectWithSpy() {
        // Create a real NotificationSender
        NotificationSender realSender = new NotificationSender() {
            @Override
            public void sendEmail(String recipient, String subject, String body) {
                System.out.println("Real sender sending: " + subject + " to " + recipient);
                // Imagine this does actual network I/O
            }
        };

        // Create a spy on the real sender
        NotificationSender spySender = spy(realSender);

        NotificationService service = new NotificationService(spySender);
        User testUser = new User("David", "david@example.com");

        // Stub one method on the spy
        doNothing().when(spySender).sendEmail(eq("david@example.com"), contains("Welcome"), anyString());

        service.sendWelcomeEmail(testUser);

        // Verify that the stubbed method was called
        verify(spySender).sendEmail(eq("david@example.com"), contains("Welcome, David!"), contains("Hi David"));

        // Because we stubbed sendEmail, the real implementation *wasn't* called for David.
        // If we hadn't stubbed it, the real sender's output would have appeared.
    }
}

Spies are useful when you want to test a class that has dependencies and you want to mock some of those dependencies, but still exercise the real logic of the class under test or its collaborators. You can use spy with when or doAnswer just like with mocks, but the default behavior is to call the real method.

The distinction between mocks and spies is crucial: mocks are "fake" objects that only do what you tell them to, while spies are real objects that you can selectively override.

When using ArgumentCaptor, remember that getValue() retrieves the last captured argument. If a method is called multiple times within the same verify block, you might need getAllValues() to get a list of all captured arguments.

The true power of Answer lies in its ability to create complex, stateful mock behavior that mimics real-world interactions, like simulating network latency, database contention, or even partial failures without needing actual external systems.

Want structured learning?

Take the full Java course →