Mocking in Java with Mockito
Mockito is the standard mocking library for Java. It integrates seamlessly with JUnit 5 and lets you replace real dependencies with controlled fakes. When your service class depends on a repository that talks to a database, Mockito lets you define exactly what that repository returns for each test — without starting a database.
Adding Mockito
Maven
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.12.0</version>
<scope>test</scope>
</dependency>
mockito-junit-jupiter includes the core Mockito library plus the JUnit 5 extension that processes @Mock, @Spy, and @InjectMocks annotations automatically.
Gradle
testImplementation("org.mockito:mockito-junit-jupiter:5.12.0")
Enable the Extension
Add @ExtendWith(MockitoExtension.class) to your test class:
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
// @Mock and @InjectMocks work here
}
Creating Mocks
@Mock Annotation
@Mock creates a mock of the annotated type. All methods return defaults (null, 0, false, or empty collections) unless you configure them.
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private EmailService emailService;
@Test
void findByIdReturnsUser() {
// Will be covered with when() below
}
}
Programmatic Mock Creation
You can also create mocks programmatically when you do not use the extension:
UserRepository userRepository = Mockito.mock(UserRepository.class);
@InjectMocks — Injecting Mocks into the Class Under Test
@InjectMocks creates an instance of the class under test and injects all available @Mock fields into it. Mockito tries constructor injection first, then setter injection, then field injection.
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private EmailService emailService;
@InjectMocks
private UserService userService; // constructed with the mocks above
@Test
void registersUserAndSendsWelcomeEmail() throws Exception {
// userService has real logic; its dependencies are mocks
}
}
This is equivalent to:
UserService userService = new UserService(userRepository, emailService);
Prefer constructor injection in your production classes — it makes @InjectMocks work reliably and the dependencies explicit.
Stubbing with when().thenReturn()
Stubbing defines what a mock method returns when called with specific arguments.
import static org.mockito.Mockito.*;
@Test
void findByIdReturnsUser() {
User alice = new User(1L, "Alice", "alice@example.com");
when(userRepository.findById(1L)).thenReturn(Optional.of(alice));
Optional<User> result = userService.findById(1L);
assertThat(result).isPresent();
assertThat(result.get().getName()).isEqualTo("Alice");
}
@Test
void findByIdReturnsEmptyWhenNotFound() {
when(userRepository.findById(99L)).thenReturn(Optional.empty());
Optional<User> result = userService.findById(99L);
assertThat(result).isEmpty();
}
Argument Matchers
When you do not care about the exact argument value, use matchers from org.mockito.ArgumentMatchers:
import static org.mockito.ArgumentMatchers.*;
// Match any Long
when(userRepository.findById(anyLong())).thenReturn(Optional.of(alice));
// Match any non-null string
when(emailService.send(anyString(), anyString())).thenReturn(true);
// Match a specific type
when(userRepository.save(any(User.class))).thenReturn(alice);
// Mixing: you must use matchers for ALL arguments or none
when(repo.findByNameAndStatus(eq("Alice"), anyString()))
.thenReturn(List.of(alice));
Returning Different Values for Consecutive Calls
when(userRepository.findById(1L))
.thenReturn(Optional.of(alice)) // first call
.thenReturn(Optional.empty()) // second call
.thenThrow(new RuntimeException("DB error")); // third call
thenAnswer() — Dynamic Return Values
Use thenAnswer when the return value depends on the argument:
when(userRepository.findById(anyLong())).thenAnswer(invocation -> {
Long id = invocation.getArgument(0);
if (id < 0) throw new IllegalArgumentException("Invalid ID");
return Optional.of(new User(id, "User " + id, "user" + id + "@example.com"));
});
Verifying Interactions
verify() asserts that a mock method was called, optionally specifying how many times and with what arguments.
@Test
void registrationSendsWelcomeEmail() throws Exception {
User newUser = new User(null, "Bob", "bob@example.com");
when(userRepository.save(any(User.class)))
.thenReturn(new User(2L, "Bob", "bob@example.com"));
userService.register(newUser);
// Verify the email was sent exactly once
verify(emailService, times(1)).send("bob@example.com", "Welcome!");
// Verify the user was saved
verify(userRepository).save(newUser); // times(1) is the default
}
@Test
void noEmailSentWhenRegistrationFails() {
when(userRepository.save(any())).thenThrow(new RuntimeException("DB down"));
assertThrows(RuntimeException.class, () -> userService.register(new User()));
// Verify emailService was never touched
verifyNoInteractions(emailService);
}
Verification Modes
verify(mock, times(3)).someMethod();
verify(mock, never()).someMethod();
verify(mock, atLeast(1)).someMethod();
verify(mock, atMost(2)).someMethod();
verify(mock, atLeastOnce()).someMethod();
verifyNoMoreInteractions
After verifying the calls you care about, assert nothing unexpected happened:
verify(userRepository).save(any());
verify(emailService).send(anyString(), anyString());
verifyNoMoreInteractions(userRepository, emailService);
ArgumentCaptor — Capturing Arguments for Inspection
When the argument passed to a mock is complex and you want to inspect its fields, use ArgumentCaptor:
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
private OrderRepository orderRepository;
@InjectMocks
private OrderService orderService;
@Captor
private ArgumentCaptor<Order> orderCaptor;
@Test
void createsOrderWithCorrectTotalPrice() {
List<LineItem> items = List.of(
new LineItem("apple", 2, 1.50),
new LineItem("bread", 1, 2.00)
);
orderService.placeOrder("customer-1", items);
verify(orderRepository).save(orderCaptor.capture());
Order savedOrder = orderCaptor.getValue();
assertThat(savedOrder.getCustomerId()).isEqualTo("customer-1");
assertThat(savedOrder.getTotal()).isEqualByComparingTo("5.00");
assertThat(savedOrder.getItems()).hasSize(2);
}
}
@Captor is a shorthand annotation equivalent to ArgumentCaptor.forClass(Order.class).
Mocking Exceptions
Use thenThrow() to simulate error scenarios:
@Test
void throwsServiceExceptionWhenDbIsDown() {
when(userRepository.findById(any()))
.thenThrow(new DataAccessException("Connection refused"));
assertThatThrownBy(() -> userService.findById(1L))
.isInstanceOf(UserServiceException.class)
.hasMessageContaining("Unable to retrieve user");
}
@Test
void sendsEmailOnce_evenWhenFirstAttemptFails() {
// First call throws, second call succeeds
when(emailService.send(anyString(), anyString()))
.thenThrow(new EmailException("SMTP timeout"))
.thenReturn(true);
userService.registerWithRetry(new User(null, "Carol", "carol@example.com"));
verify(emailService, times(2)).send(anyString(), anyString());
}
For void methods, use doThrow():
// void methods cannot use when()...thenThrow()
doThrow(new AuditException("Audit failed"))
.when(auditService).log(any());
assertThrows(AuditException.class, () -> userService.deleteUser(1L));
Spy Objects
A spy wraps a real object, delegating all method calls to the original implementation by default, while allowing you to stub or verify individual methods.
@ExtendWith(MockitoExtension.class)
class CachingServiceTest {
@Spy
private List<String> spyList = new ArrayList<>();
@Test
void addElementAndVerify() {
spyList.add("one");
spyList.add("two");
verify(spyList, times(2)).add(anyString());
assertThat(spyList).hasSize(2); // real add() was called
}
@Test
void stubSizeWhileKeepingRealAdd() {
spyList.add("real element");
// Override one method; others stay real
doReturn(100).when(spyList).size();
assertThat(spyList.size()).isEqualTo(100); // stubbed
assertThat(spyList.get(0)).isEqualTo("real element"); // real
}
}
Spies are useful when you want to test a real class but stub a specific expensive method (like a network call). Use mocks by default and reach for spies only when testing code that cannot be refactored for dependency injection.
BDD-Style Stubbing with BDDMockito
Mockito ships a BDDMockito class that aligns its API with Given/When/Then:
import static org.mockito.BDDMockito.*;
@Test
void givenUserExists_whenFindById_thenReturnsUser() {
// Given
given(userRepository.findById(1L)).willReturn(Optional.of(alice));
// When
Optional<User> result = userService.findById(1L);
// Then
then(userRepository).should().findById(1L);
assertThat(result).contains(alice);
}
A Complete Example: UserService
// src/main/java/com/example/user/UserService.java
package com.example.user;
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
public UserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
public User register(String name, String email) {
if (userRepository.existsByEmail(email)) {
throw new DuplicateEmailException("Email already registered: " + email);
}
User saved = userRepository.save(new User(null, name, email));
emailService.sendWelcome(saved.getEmail(), saved.getName());
return saved;
}
public User findById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found: " + id));
}
}
// src/test/java/com/example/user/UserServiceTest.java
package com.example.user;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Optional;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private EmailService emailService;
@InjectMocks
private UserService userService;
@Captor
private ArgumentCaptor<User> userCaptor;
@Nested
@DisplayName("register")
class Register {
@Test
@DisplayName("saves the user and sends welcome email")
void savesUserAndSendsEmail() {
when(userRepository.existsByEmail("alice@example.com")).thenReturn(false);
when(userRepository.save(any(User.class)))
.thenReturn(new User(1L, "Alice", "alice@example.com"));
userService.register("Alice", "alice@example.com");
verify(userRepository).save(userCaptor.capture());
assertThat(userCaptor.getValue().getName()).isEqualTo("Alice");
verify(emailService).sendWelcome("alice@example.com", "Alice");
}
@Test
@DisplayName("throws DuplicateEmailException when email already registered")
void throwsWhenEmailAlreadyExists() {
when(userRepository.existsByEmail("alice@example.com")).thenReturn(true);
assertThatThrownBy(() -> userService.register("Alice", "alice@example.com"))
.isInstanceOf(DuplicateEmailException.class)
.hasMessageContaining("alice@example.com");
verifyNoInteractions(emailService);
}
}
@Nested
@DisplayName("findById")
class FindById {
@Test
@DisplayName("returns the user when found")
void returnsUserWhenFound() {
User alice = new User(1L, "Alice", "alice@example.com");
when(userRepository.findById(1L)).thenReturn(Optional.of(alice));
User result = userService.findById(1L);
assertThat(result.getName()).isEqualTo("Alice");
}
@Test
@DisplayName("throws UserNotFoundException when user does not exist")
void throwsWhenNotFound() {
when(userRepository.findById(99L)).thenReturn(Optional.empty());
assertThatThrownBy(() -> userService.findById(99L))
.isInstanceOf(UserNotFoundException.class)
.hasMessageContaining("99");
}
}
}
This pattern — @Mock for dependencies, @InjectMocks for the class under test, when() for stubbing, verify() for interaction checks — covers the vast majority of Java unit testing scenarios. Chapter 6 moves on to integration tests where some of these mocks are replaced with real infrastructure.