Mockito is the most popular mocking framework for Java unit tests. It allows you to create and configure mock objects to test components in isolation.
1. Mockito Setup and Dependencies
Maven Dependencies
<!-- JUnit 5 --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.9.2</version> <scope>test</scope> </dependency> <!-- Mockito --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>5.3.1</version> <scope>test</scope> </dependency> <!-- Mockito JUnit 5 Integration --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <version>5.3.1</version> <scope>test</scope> </dependency>
Gradle Dependencies
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' testImplementation 'org.mockito:mockito-core:5.3.1' testImplementation 'org.mockito:mockito-junit-jupiter:5.3.1'
2. Basic Mocking with Mockito
Creating Mocks
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.util.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class BasicMockingTest { // Different ways to create mocks @Mock private List<String> mockedList; // Using annotation @Test void testBasicMocking() { // Using mock() method List<String> mockList = mock(List.class); // Configure mock behavior when(mockList.get(0)).thenReturn("first"); when(mockList.get(1)).thenReturn("second"); when(mockList.size()).thenReturn(100); // Verify mock behavior assertEquals("first", mockList.get(0)); assertEquals("second", mockList.get(1)); assertEquals(100, mockList.size()); // Default values for non-stubbed methods assertNull(mockList.get(999)); // Returns null for non-stubbed calls } @Test void testMockWithAnnotation() { // @Mock annotated field is automatically initialized when(mockedList.size()).thenReturn(50); assertEquals(50, mockedList.size()); } @Test void testVoidMethods() { List<String> mockList = mock(List.class); // For void methods, use doNothing(), doThrow(), etc. doNothing().when(mockList).clear(); doThrow(new RuntimeException("Clear failed")).when(mockList).add(anyString()); // This won't throw exception mockList.clear(); // This will throw exception assertThrows(RuntimeException.class, () -> mockList.add("test")); } } 3. Stubbing Method Calls
Different Stubbing Approaches
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.util.Arrays; import java.util.List; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class StubbingTest { @Mock private List<String> mockList; @Test void testBasicStubbing() { // Stub with specific return value when(mockList.get(0)).thenReturn("first"); when(mockList.get(1)).thenReturn("second"); assertEquals("first", mockList.get(0)); assertEquals("second", mockList.get(1)); } @Test void testConsecutiveCalls() { // Different return values for consecutive calls when(mockList.size()) .thenReturn(1) .thenReturn(2) .thenReturn(3); assertEquals(1, mockList.size()); assertEquals(2, mockList.size()); assertEquals(3, mockList.size()); assertEquals(3, mockList.size()); // Last value repeats } @Test void testStubbingWithExceptions() { // Stub to throw exception when(mockList.get(anyInt())).thenThrow(new RuntimeException("Database error")); assertThrows(RuntimeException.class, () -> mockList.get(0)); assertThrows(RuntimeException.class, () -> mockList.get(1)); } @Test void testStubbingWithAnswer() { // Custom logic for stubbed method when(mockList.get(anyInt())).thenAnswer(invocation -> { int index = invocation.getArgument(0); return "element-" + index; }); assertEquals("element-0", mockList.get(0)); assertEquals("element-5", mockList.get(5)); } @Test void testStubbingVoidMethods() { // For void methods, use do...when pattern doThrow(new RuntimeException("Clear failed")).when(mockList).clear(); assertThrows(RuntimeException.class, () -> mockList.clear()); } @Test void testStubbingWithRealMethodCall() { List<String> realList = Arrays.asList("A", "B", "C"); List<String> spyList = spy(realList); // Spy on real object // Stub some methods, call real methods for others when(spyList.size()).thenReturn(100); doReturn("X").when(spyList).get(0); assertEquals("X", spyList.get(0)); // Stubbed assertEquals("B", spyList.get(1)); // Real method assertEquals(100, spyList.size()); // Stubbed } } 4. Argument Matchers
Using Argument Matchers
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.util.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class ArgumentMatchersTest { @Mock private List<String> mockList; @Mock private Map<String, String> mockMap; @Test void testBasicArgumentMatchers() { // any(), anyString(), anyInt(), etc. when(mockList.get(anyInt())).thenReturn("element"); when(mockMap.put(anyString(), anyString())).thenReturn("oldValue"); assertEquals("element", mockList.get(0)); assertEquals("element", mockList.get(999)); assertEquals("oldValue", mockMap.put("key", "value")); } @Test void testSpecificMatchers() { // eq() for specific values when(mockList.set(eq(0), eq("newValue"))).thenReturn("oldValue"); // startsWith(), endsWith(), contains() for strings when(mockMap.containsKey(startsWith("test"))).thenReturn(true); assertEquals("oldValue", mockList.set(0, "newValue")); assertTrue(mockMap.containsKey("test123")); } @Test void testCustomMatchers() { // Custom argument matcher when(mockList.get(argThat(index -> index % 2 == 0))).thenReturn("even"); when(mockList.get(argThat(index -> index % 2 != 0))).thenReturn("odd"); assertEquals("even", mockList.get(0)); assertEquals("odd", mockList.get(1)); assertEquals("even", mockList.get(2)); } @Test void testArgumentCaptor() { // Capture arguments for verification ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); mockList.add("test"); mockList.add("another"); // Verify and capture arguments verify(mockList, times(2)).add(captor.capture()); List<String> allValues = captor.getAllValues(); assertEquals(2, allValues.size()); assertEquals("test", allValues.get(0)); assertEquals("another", allValues.get(1)); } @Test void testMixedMatchers() { // Mix specific values and matchers when(mockList.set(anyInt(), eq("specific"))).thenReturn("old"); assertEquals("old", mockList.set(0, "specific")); assertEquals("old", mockList.set(999, "specific")); // Verify with mixed matchers verify(mockList, times(2)).set(anyInt(), eq("specific")); } } 5. Verification with Mockito
Verifying Mock Interactions
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.util.List; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class VerificationTest { @Mock private List<String> mockList; @Test void testBasicVerification() { // Perform actions mockList.add("one"); mockList.add("two"); mockList.clear(); // Verify interactions verify(mockList).add("one"); verify(mockList).add("two"); verify(mockList).clear(); // Verify number of invocations verify(mockList, times(2)).add(anyString()); verify(mockList, times(1)).clear(); // Verify no more interactions verifyNoMoreInteractions(mockList); } @Test void testVerificationModes() { mockList.add("test"); mockList.add("test"); mockList.add("another"); // Different verification modes verify(mockList, times(3)).add(anyString()); // Exactly 3 times verify(mockList, atLeast(2)).add("test"); // At least 2 times verify(mockList, atLeastOnce()).add("another"); // At least once verify(mockList, atMost(5)).add(anyString()); // At most 5 times // Verify no interactions verify(mockList, never()).remove(anyString()); } @Test void testVerificationInOrder() { // Verify calls in specific order mockList.add("first"); mockList.add("second"); mockList.clear(); InOrder inOrder = inOrder(mockList); inOrder.verify(mockList).add("first"); inOrder.verify(mockList).add("second"); inOrder.verify(mockList).clear(); } @Test void testVerificationWithTimeout() { // Useful for asynchronous code new Thread(() -> { try { Thread.sleep(100); mockList.add("async"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start(); // Verify within timeout verify(mockList, timeout(500)).add("async"); } @Test void testVerificationOfZeroInteractions() { // Verify no interactions with mock verifyNoInteractions(mockList); mockList.size(); // This would make the test fail if uncommented } } 6. Real-World Testing Examples
Service Layer Testing
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.Arrays; import java.util.List; import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; // Domain classes class User { private Long id; private String name; private String email; public User(Long id, String name, String email) { this.id = id; this.name = name; this.email = email; } // Getters and setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } } // Repository interface interface UserRepository { Optional<User> findById(Long id); List<User> findAll(); User save(User user); void deleteById(Long id); boolean existsByEmail(String email); } // Service class class UserService { private final UserRepository userRepository; private final EmailService emailService; public UserService(UserRepository userRepository, EmailService emailService) { this.userRepository = userRepository; this.emailService = emailService; } public User createUser(String name, String email) { if (userRepository.existsByEmail(email)) { throw new IllegalArgumentException("Email already exists: " + email); } User user = new User(null, name, email); User savedUser = userRepository.save(user); // Send welcome email emailService.sendWelcomeEmail(savedUser.getEmail(), savedUser.getName()); return savedUser; } public User getUserById(Long id) { return userRepository.findById(id) .orElseThrow(() -> new RuntimeException("User not found with id: " + id)); } public List<User> getAllUsers() { return userRepository.findAll(); } public void deleteUser(Long id) { if (!userRepository.findById(id).isPresent()) { throw new RuntimeException("User not found with id: " + id); } userRepository.deleteById(id); } } // Email service interface interface EmailService { void sendWelcomeEmail(String email, String name); void sendNotification(String email, String message); } @ExtendWith(MockitoExtension.class) public class UserServiceTest { @Mock private UserRepository userRepository; @Mock private EmailService emailService; @InjectMocks private UserService userService; @Test void testCreateUser_Success() { // Setup String name = "John Doe"; String email = "[email protected]"; User savedUser = new User(1L, name, email); when(userRepository.existsByEmail(email)).thenReturn(false); when(userRepository.save(any(User.class))).thenReturn(savedUser); // Execute User result = userService.createUser(name, email); // Verify assertNotNull(result); assertEquals(1L, result.getId()); assertEquals(name, result.getName()); assertEquals(email, result.getEmail()); verify(userRepository).existsByEmail(email); verify(userRepository).save(any(User.class)); verify(emailService).sendWelcomeEmail(email, name); } @Test void testCreateUser_EmailExists() { // Setup String name = "John Doe"; String email = "[email protected]"; when(userRepository.existsByEmail(email)).thenReturn(true); // Execute & Verify IllegalArgumentException exception = assertThrows( IllegalArgumentException.class, () -> userService.createUser(name, email) ); assertEquals("Email already exists: " + email, exception.getMessage()); verify(userRepository).existsByEmail(email); verify(userRepository, never()).save(any(User.class)); verify(emailService, never()).sendWelcomeEmail(anyString(), anyString()); } @Test void testGetUserById_Found() { // Setup Long userId = 1L; User user = new User(userId, "John Doe", "[email protected]"); when(userRepository.findById(userId)).thenReturn(Optional.of(user)); // Execute User result = userService.getUserById(userId); // Verify assertNotNull(result); assertEquals(userId, result.getId()); verify(userRepository).findById(userId); } @Test void testGetUserById_NotFound() { // Setup Long userId = 999L; when(userRepository.findById(userId)).thenReturn(Optional.empty()); // Execute & Verify RuntimeException exception = assertThrows( RuntimeException.class, () -> userService.getUserById(userId) ); assertEquals("User not found with id: " + userId, exception.getMessage()); verify(userRepository).findById(userId); } @Test void testGetAllUsers() { // Setup List<User> users = Arrays.asList( new User(1L, "John Doe", "[email protected]"), new User(2L, "Jane Smith", "[email protected]") ); when(userRepository.findAll()).thenReturn(users); // Execute List<User> result = userService.getAllUsers(); // Verify assertEquals(2, result.size()); verify(userRepository).findAll(); } @Test void testDeleteUser_Success() { // Setup Long userId = 1L; User user = new User(userId, "John Doe", "[email protected]"); when(userRepository.findById(userId)).thenReturn(Optional.of(user)); // Execute userService.deleteUser(userId); // Verify verify(userRepository).findById(userId); verify(userRepository).deleteById(userId); } @Test void testDeleteUser_NotFound() { // Setup Long userId = 999L; when(userRepository.findById(userId)).thenReturn(Optional.empty()); // Execute & Verify RuntimeException exception = assertThrows( RuntimeException.class, () -> userService.deleteUser(userId) ); assertEquals("User not found with id: " + userId, exception.getMessage()); verify(userRepository).findById(userId); verify(userRepository, never()).deleteById(anyLong()); } } 7. Advanced Mockito Features
Spies, Partial Mocks, and Deep Stubs
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.util.ArrayList; import java.util.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class AdvancedMockitoTest { @Test void testSpyOnRealObject() { // Spy on real object - partial mocking List<String> realList = new ArrayList<>(); List<String> spyList = spy(realList); // You can stub some methods and call real methods for others doReturn(100).when(spyList).size(); // Real method call spyList.add("real element"); assertEquals(1, realList.size()); // Real list has one element assertEquals(100, spyList.size()); // Stubbed method verify(spyList).add("real element"); verify(spyList).size(); } @Test void testMockWithDeepStubs() { // Deep stubbing for method chains List<List<String>> deepMock = mock(List.class, RETURNS_DEEP_STUBS); when(deepMock.get(0).get(0)).thenReturn("deep value"); assertEquals("deep value", deepMock.get(0).get(0)); } @Test void testResetMock() { List<String> mockList = mock(List.class); when(mockList.size()).thenReturn(10); assertEquals(10, mockList.size()); // Reset mock reset(mockList); // Now returns default value assertEquals(0, mockList.size()); } @Test void testMockWithDefaultAnswer() { // Custom default behavior List<String> mockList = mock(List.class, invocation -> { if (invocation.getMethod().getReturnType() == int.class) { return 42; // Default return for int methods } return null; // Default for other methods }); assertEquals(42, mockList.size()); assertNull(mockList.get(0)); } } // Testing with complex dependencies class OrderService { private final PaymentService paymentService; private final InventoryService inventoryService; private final NotificationService notificationService; public OrderService(PaymentService paymentService, InventoryService inventoryService, NotificationService notificationService) { this.paymentService = paymentService; this.inventoryService = inventoryService; this.notificationService = notificationService; } public Order processOrder(Order order) { // Check inventory if (!inventoryService.isAvailable(order.getProductId(), order.getQuantity())) { throw new RuntimeException("Product not available"); } // Process payment PaymentResult paymentResult = paymentService.processPayment( order.getCustomerId(), order.getAmount()); if (!paymentResult.isSuccess()) { throw new RuntimeException("Payment failed: " + paymentResult.getErrorMessage()); } // Update inventory inventoryService.reserveProduct(order.getProductId(), order.getQuantity()); // Send notification notificationService.sendOrderConfirmation(order.getCustomerId(), order.getId()); order.setStatus(OrderStatus.CONFIRMED); return order; } } // Supporting classes for OrderService test class Order { private String id; private String customerId; private String productId; private int quantity; private double amount; private OrderStatus status; // Constructors, getters, setters public Order(String id, String customerId, String productId, int quantity, double amount) { this.id = id; this.customerId = customerId; this.productId = productId; this.quantity = quantity; this.amount = amount; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getCustomerId() { return customerId; } public void setCustomerId(String customerId) { this.customerId = customerId; } public String getProductId() { return productId; } public void setProductId(String productId) { this.productId = productId; } public int getQuantity() { return quantity; } public void setQuantity(int quantity) { this.quantity = quantity; } public double getAmount() { return amount; } public void setAmount(double amount) { this.amount = amount; } public OrderStatus getStatus() { return status; } public void setStatus(OrderStatus status) { this.status = status; } } enum OrderStatus { PENDING, CONFIRMED, SHIPPED, DELIVERED } class PaymentResult { private boolean success; private String errorMessage; public PaymentResult(boolean success, String errorMessage) { this.success = success; this.errorMessage = errorMessage; } public boolean isSuccess() { return success; } public String getErrorMessage() { return errorMessage; } } interface PaymentService { PaymentResult processPayment(String customerId, double amount); } interface InventoryService { boolean isAvailable(String productId, int quantity); void reserveProduct(String productId, int quantity); } interface NotificationService { void sendOrderConfirmation(String customerId, String orderId); } @ExtendWith(MockitoExtension.class) class OrderServiceTest { @Mock private PaymentService paymentService; @Mock private InventoryService inventoryService; @Mock private NotificationService notificationService; @InjectMocks private OrderService orderService; @Test void testProcessOrder_Success() { // Setup Order order = new Order("123", "customer1", "product1", 2, 100.0); PaymentResult paymentResult = new PaymentResult(true, null); when(inventoryService.isAvailable("product1", 2)).thenReturn(true); when(paymentService.processPayment("customer1", 100.0)).thenReturn(paymentResult); // Execute Order result = orderService.processOrder(order); // Verify assertEquals(OrderStatus.CONFIRMED, result.getStatus()); // Verify interactions verify(inventoryService).isAvailable("product1", 2); verify(paymentService).processPayment("customer1", 100.0); verify(inventoryService).reserveProduct("product1", 2); verify(notificationService).sendOrderConfirmation("customer1", "123"); } @Test void testProcessOrder_InventoryUnavailable() { // Setup Order order = new Order("123", "customer1", "product1", 2, 100.0); when(inventoryService.isAvailable("product1", 2)).thenReturn(false); // Execute & Verify RuntimeException exception = assertThrows( RuntimeException.class, () -> orderService.processOrder(order) ); assertEquals("Product not available", exception.getMessage()); verify(inventoryService).isAvailable("product1", 2); verify(paymentService, never()).processPayment(anyString(), anyDouble()); verify(inventoryService, never()).reserveProduct(anyString(), anyInt()); verify(notificationService, never()).sendOrderConfirmation(anyString(), anyString()); } @Test void testProcessOrder_PaymentFailed() { // Setup Order order = new Order("123", "customer1", "product1", 2, 100.0); PaymentResult paymentResult = new PaymentResult(false, "Insufficient funds"); when(inventoryService.isAvailable("product1", 2)).thenReturn(true); when(paymentService.processPayment("customer1", 100.0)).thenReturn(paymentResult); // Execute & Verify RuntimeException exception = assertThrows( RuntimeException.class, () -> orderService.processOrder(order) ); assertEquals("Payment failed: Insufficient funds", exception.getMessage()); verify(inventoryService).isAvailable("product1", 2); verify(paymentService).processPayment("customer1", 100.0); verify(inventoryService, never()).reserveProduct(anyString(), anyInt()); verify(notificationService, never()).sendOrderConfirmation(anyString(), anyString()); } } 8. Best Practices and Common Patterns
Test Configuration Class
import org.mockito.Mockito; // Reusable test configurations class TestConfig { static UserRepository createUserRepositoryMock() { UserRepository mockRepo = Mockito.mock(UserRepository.class); // Common stubbing when(mockRepo.existsByEmail("[email protected]")).thenReturn(true); when(mockRepo.existsByEmail("[email protected]")).thenReturn(false); return mockRepo; } static EmailService createEmailServiceMock() { EmailService mockService = Mockito.mock(EmailService.class); // Verify common interactions doNothing().when(mockService).sendWelcomeEmail(anyString(), anyString()); return mockService; } } // Using static imports for cleaner tests import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static org.junit.jupiter.api.Assertions.*; class CleanTestExample { @Test void testCleanSyntax() { // Given List<String> mockList = mock(List.class); when(mockList.get(anyInt())).thenReturn("value"); // When String result = mockList.get(0); // Then assertEquals("value", result); verify(mockList).get(0); } } Key Mockito Features Summary
- Mock Creation -
mock(),@Mock,spy() - Stubbing -
when().thenReturn(),doReturn().when() - Verification -
verify(),times(),never() - Argument Matchers -
any(),eq(),argThat() - Argument Captors - Capture and verify arguments
- InOrder Verification - Verify call sequences
- Spies - Partial mocking of real objects
- Answer Interface - Custom stubbing logic
Mockito helps create clean, maintainable tests by isolating the code under test and verifying interactions with dependencies.