Mockito
doAnswer
thenReturn
Java testing
mocking framework

Mockito doAnswer Vs thenReturn

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

thenReturn and doAnswer can both make a Mockito mock return something, but they are not interchangeable in intent. thenReturn is the default choice for fixed, simple behavior. doAnswer is the escape hatch for behavior that depends on invocation arguments, mutates state, or handles void methods. If you start from that distinction, most Mockito tests become easier to read.

Use thenReturn for Simple, Fixed Outcomes

thenReturn is ideal when the return value is known ahead of time and does not depend on runtime details.

java
1import static org.mockito.Mockito.*;
2import static org.junit.jupiter.api.Assertions.*;
3
4import java.util.List;
5import org.junit.jupiter.api.Test;
6
7class ThenReturnTest {
8    @Test
9    void returnsFixedValue() {
10        List<String> list = mock(List.class);
11        when(list.get(0)).thenReturn("Mockito");
12
13        assertEquals("Mockito", list.get(0));
14    }
15}

This is the most readable form because the test says exactly what matters: when this method is called, return this value.

Use thenReturn when:

  • the value is static
  • the behavior should be obvious at a glance
  • you want the test to stay short and declarative

If this is enough, stop there.

Use doAnswer When the Result Depends on the Call

doAnswer is more flexible because it gives access to the invocation and arguments.

java
1import static org.mockito.Mockito.*;
2import static org.junit.jupiter.api.Assertions.*;
3
4import java.util.List;
5import org.junit.jupiter.api.Test;
6
7class DoAnswerTest {
8    @Test
9    void returnsValueDerivedFromArgument() {
10        List<String> list = mock(List.class);
11
12        doAnswer(invocation -> {
13            int index = invocation.getArgument(0);
14            return "value-" + index;
15        }).when(list).get(anyInt());
16
17        assertEquals("value-0", list.get(0));
18        assertEquals("value-3", list.get(3));
19    }
20}

This is useful when the return value cannot be expressed as one fixed literal.

Typical reasons to use doAnswer include:

  • computing a value from arguments
  • triggering callbacks passed into the method
  • mutating a captured test object
  • stubbing void methods with side effects

Void Methods Are a Common Reason to Reach for doAnswer

thenReturn works only for methods with return values. For void methods, Mockito's do... family is the normal API shape.

java
1import static org.mockito.Mockito.*;
2import static org.junit.jupiter.api.Assertions.*;
3
4import org.junit.jupiter.api.Test;
5
6interface EventBus {
7    void publish(String topic, String payload);
8}
9
10class VoidAnswerTest {
11    @Test
12    void capturesVoidInvocation() {
13        EventBus bus = mock(EventBus.class);
14        StringBuilder seen = new StringBuilder();
15
16        doAnswer(invocation -> {
17            String topic = invocation.getArgument(0);
18            String payload = invocation.getArgument(1);
19            seen.append(topic).append(':').append(payload);
20            return null;
21        }).when(bus).publish(anyString(), anyString());
22
23        bus.publish("orders", "created");
24        assertEquals("orders:created", seen.toString());
25    }
26}

This is one of the clearest cases where doAnswer is not just optional but structurally appropriate.

Readability Is the Real Tradeoff

The tradeoff is not performance. It is test clarity.

thenReturn reads like a fact. doAnswer reads like custom behavior.

That means the decision rule is simple:

  • prefer thenReturn whenever it expresses the whole stub clearly
  • use doAnswer only when the behavior really is dynamic or side-effectful

Tests become harder to maintain when doAnswer is used for trivial cases that could have been one line with thenReturn.

Beware of Overpowering the Mock

Because doAnswer can do almost anything, it is easy to smuggle application logic into the test double. That is usually a smell.

If your answer block contains lots of branching, business rules, or duplicated production behavior, the test is probably doing too much. At that point, a fake implementation or a different test shape may be clearer than an elaborate Mockito stub.

Interaction with Exceptions

Both styles can participate in exception testing, but the shape differs.

Simple exception case with the when style:

java
when(service.load()).thenThrow(new IllegalStateException("boom"));

Dynamic exception logic with doAnswer:

java
1doAnswer(invocation -> {
2    String id = invocation.getArgument(0);
3    if (id.isBlank()) {
4        throw new IllegalArgumentException("id required");
5    }
6    return "ok";
7}).when(service).find(anyString());

Again, the point is not that doAnswer is better. It is that it is more expressive when the response depends on inputs.

Common Pitfalls

The biggest mistake is using doAnswer for a fixed return value. That adds noise to the test and hides the intent.

Another mistake is forgetting that doAnswer for void methods must return null. Mockito expects the lambda to satisfy the answer contract even though the mocked method itself is void.

Developers also sometimes write large answer blocks that duplicate production logic. At that point the mock stops being a simple test seam and becomes a second implementation.

Finally, do not choose between the two based on style alone. Choose based on whether the stubbed behavior is fixed or invocation-dependent.

Summary

  • 'thenReturn is the default for simple fixed return values.'
  • 'doAnswer is for dynamic behavior, argument-dependent results, side effects, and void methods.'
  • Prefer the simplest stub that expresses the test intent clearly.
  • Avoid putting real business logic inside doAnswer blocks.
  • If the behavior is static, thenReturn is usually the right answer.

Course illustration
Course illustration

All Rights Reserved.