Mocking with Mockito
Real-world objects rarely stand alone. A OrderService talks to a PaymentGateway; a UserService talks to a UserRepository. When unit-testing OrderService you want to verify its logic in isolation — without hitting a payment API or a database. That is exactly what Mockito enables: it generates stand-in objects whose behaviour you control entirely.
What is a Mock?
A mock is a dynamically generated implementation of an interface or class. By default every method on a mock does nothing (returns null, 0, or an empty collection depending on the return type). You then stub individual methods to return whatever the test needs, and later you can verify that specific calls were made. This lesson focuses on creating mocks and stubbing; verification is covered in lesson 7.
Mock vs. Stub vs. Spy: In Mockito's vocabulary a mock is a full stand-in where all methods are dummies unless stubbed; a spy wraps a real object so real methods are called by default unless overridden; a stub is just a pre-configured return value. The @Mock annotation gives you a pure mock.
Adding Mockito to Your Project
If you use Spring Boot, spring-boot-starter-test already includes Mockito. For a plain Maven project add the JUnit 5 integration artifact:
<!-- pom.xml -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
Creating Mocks
There are two equally valid ways to create a mock in JUnit 5.
1. Programmatic (inline): best when you need just one or two mocks and prefer everything visible at the point of use.
import org.mockito.Mockito;
UserRepository repo = Mockito.mock(UserRepository.class);
2. Annotation-driven: the idiomatic choice for larger test classes. Annotate the class with @ExtendWith(MockitoExtension.class) and declare fields with @Mock. Mockito initialises them before each test automatically.
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
PaymentGateway paymentGateway; // stand-in for the real gateway
@Mock
OrderRepository orderRepository; // stand-in for the real DB layer
@InjectMocks
OrderService orderService; // the class under test — Mockito injects the mocks
}
@InjectMocks tells Mockito to instantiate OrderService and inject the declared mocks via constructor injection (preferred), setter injection, or field injection — in that priority order. This means you never call new OrderService(...) yourself in the test.
Prefer constructor injection in production code. When OrderService declares a constructor that accepts its dependencies, @InjectMocks calls that constructor and injection is explicit. Field injection makes the dependency invisible and harder to reason about.
Stubbing with when / thenReturn
Stubbing is the act of telling a mock "when this method is called with these arguments, return this value". The syntax is deliberately readable:
import static org.mockito.Mockito.when;
// Stub: when findById("u42") is called, return a known User
when(orderRepository.findById("o99")).thenReturn(Optional.of(new Order("o99", 150.00)));
Read it left-to-right: when orderRepository.findById("o99") is called, then return an Optional containing a test order. The mock will return this value every time that method is called with exactly "o99" during the test.
A Complete Example
// Production classes (simplified)
public record Order(String id, double amount) {}
public interface OrderRepository {
Optional<Order> findById(String id);
void save(Order order);
}
public interface PaymentGateway {
boolean charge(String orderId, double amount);
}
public class OrderService {
private final OrderRepository repository;
private final PaymentGateway gateway;
public OrderService(OrderRepository repository, PaymentGateway gateway) {
this.repository = repository;
this.gateway = gateway;
}
public boolean processOrder(String orderId) {
Order order = repository.findById(orderId)
.orElseThrow(() -> new IllegalArgumentException("Order not found: " + orderId));
return gateway.charge(order.id(), order.amount());
}
}
// Test class
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock OrderRepository orderRepository;
@Mock PaymentGateway paymentGateway;
@InjectMocks OrderService orderService;
@Test
void processOrder_chargesWhenOrderExists() {
// Arrange: stub both collaborators
Order order = new Order("o99", 150.00);
when(orderRepository.findById("o99")).thenReturn(Optional.of(order));
when(paymentGateway.charge("o99", 150.00)).thenReturn(true);
// Act
boolean result = orderService.processOrder("o99");
// Assert
assertTrue(result);
}
@Test
void processOrder_returnsFalseWhenGatewayDeclines() {
Order order = new Order("o99", 150.00);
when(orderRepository.findById("o99")).thenReturn(Optional.of(order));
when(paymentGateway.charge("o99", 150.00)).thenReturn(false); // gateway declines
assertFalse(orderService.processOrder("o99"));
}
}
Argument Matchers
Sometimes you do not care about the exact argument — you want the stub to fire for any string, or any non-null value. Mockito provides argument matchers from org.mockito.ArgumentMatchers:
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
// Match any String argument
when(orderRepository.findById(anyString())).thenReturn(Optional.empty());
// Match a specific value alongside any second argument
when(paymentGateway.charge(eq("o99"), any(Double.class))).thenReturn(true);
Mixing matchers and raw values is illegal. If you use a matcher for one argument you must use matchers for ALL arguments in that call. when(gateway.charge("o99", anyDouble())) will throw a InvalidUseOfMatchersException at runtime. Use eq("o99") to wrap the literal.
Stubbing Exceptions and Consecutive Calls
Stubs are not limited to returning values. You can also throw exceptions or return different values on successive calls:
import static org.mockito.Mockito.when;
// Throw on first call, return value on second
when(orderRepository.findById("bad"))
.thenThrow(new RuntimeException("DB timeout"))
.thenReturn(Optional.empty()); // fallback on retry
// Simpler: always throw
when(paymentGateway.charge(anyString(), anyDouble()))
.thenThrow(new PaymentException("Gateway unreachable"));
Stubbing void Methods
A method that returns void cannot appear on the left side of when(...).thenReturn(). Use doThrow (or doNothing) instead:
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
// Explicitly do nothing (the default, but sometimes useful for clarity)
doNothing().when(orderRepository).save(any(Order.class));
// Make the void method throw
doThrow(new RuntimeException("Write failed"))
.when(orderRepository).save(any(Order.class));
Default Return Values
Unstubbed methods return Mockito's defaults: null for objects, 0 / false for primitives, and empty collections for List, Map, Set, etc. Relying on these silently can mask bugs — stub explicitly when the return value matters to the assertion.
Summary
Mockito lets you isolate the system under test by replacing real collaborators with mocks. Use @ExtendWith(MockitoExtension.class) with @Mock and @InjectMocks for annotation-driven setup. Stub methods with when(mock.method(args)).thenReturn(value) and use argument matchers for flexible matching. For void methods use the doXxx().when(mock).method() form. In the next lesson you will learn how to verify that the right calls were made on your mocks.