google-site-verification: google61fe8ba583a51912.html
Mocking with Mockito in Java

Mockito is the most popular mocking framework for Java unit tests. It allows you to create and configure mock objects, verify interactions, and stub method calls, making it easier to test components in isolation.

Dependencies Setup

Maven Dependencies

<properties> <mockito.version>5.8.0</mockito.version> <junit.version>5.10.0</junit.version> </properties> <dependencies> <!-- Mockito Core --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>${mockito.version}</version> <scope>test</scope> </dependency> <!-- Mockito JUnit 5 Integration --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <version>${mockito.version}</version> <scope>test</scope> </dependency> <!-- JUnit 5 --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <!-- AssertJ for fluent assertions --> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.24.2</version> <scope>test</scope> </dependency> </dependencies>

Basic Mockito Usage

Example 1: Basic Mock Creation and Stubbing

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 static org.mockito.Mockito.*; import static org.junit.jupiter.api.Assertions.*; @ExtendWith(MockitoExtension.class) class BasicMockitoTest { @Mock private List<String> mockedList; @Test void testBasicMockBehavior() { // Configure mock behavior when(mockedList.size()).thenReturn(100); when(mockedList.get(0)).thenReturn("first element"); when(mockedList.get(1)).thenReturn("second element"); // Use the mock mockedList.add("one"); // Verify interactions and assert behavior assertEquals(100, mockedList.size()); assertEquals("first element", mockedList.get(0)); assertEquals("second element", mockedList.get(1)); // Verify interactions verify(mockedList).add("one"); verify(mockedList, times(1)).add("one"); verify(mockedList, atLeastOnce()).size(); verify(mockedList, never()).clear(); } @Test void testArgumentMatchers() { when(mockedList.get(anyInt())).thenReturn("element"); assertEquals("element", mockedList.get(0)); assertEquals("element", mockedList.get(999)); verify(mockedList, times(2)).get(anyInt()); } @Test void testVoidMethods() { // For void methods, use doNothing(), doThrow(), etc. doThrow(new RuntimeException("Boom!")).when(mockedList).clear(); assertThrows(RuntimeException.class, () -> mockedList.clear()); } }

Real-World Testing Examples

Example 2: Service Layer Testing

import org.junit.jupiter.api.BeforeEach; 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.math.BigDecimal; import java.time.LocalDateTime; import java.util.Optional; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static org.assertj.core.api.Assertions.*; // Domain Classes class User { private Long id; private String username; private String email; private BigDecimal balance; public User(Long id, String username, String email, BigDecimal balance) { this.id = id; this.username = username; this.email = email; this.balance = balance; } // Getters and setters public Long getId() { return id; } public String getUsername() { return username; } public String getEmail() { return email; } public BigDecimal getBalance() { return balance; } public void setBalance(BigDecimal balance) { this.balance = balance; } } class Transaction { private Long id; private Long userId; private BigDecimal amount; private String type; private LocalDateTime timestamp; public Transaction(Long id, Long userId, BigDecimal amount, String type, LocalDateTime timestamp) { this.id = id; this.userId = userId; this.amount = amount; this.type = type; this.timestamp = timestamp; } // Getters public Long getId() { return id; } public Long getUserId() { return userId; } public BigDecimal getAmount() { return amount; } public String getType() { return type; } public LocalDateTime getTimestamp() { return timestamp; } } // Repository Interfaces interface UserRepository { Optional<User> findById(Long id); User save(User user); } interface TransactionRepository { Transaction save(Transaction transaction); } // Service Class class PaymentService { private final UserRepository userRepository; private final TransactionRepository transactionRepository; public PaymentService(UserRepository userRepository, TransactionRepository transactionRepository) { this.userRepository = userRepository; this.transactionRepository = transactionRepository; } public Transaction processPayment(Long userId, BigDecimal amount) { User user = userRepository.findById(userId) .orElseThrow(() -> new IllegalArgumentException("User not found")); if (user.getBalance().compareTo(amount) < 0) { throw new IllegalArgumentException("Insufficient balance"); } // Update user balance user.setBalance(user.getBalance().subtract(amount)); userRepository.save(user); // Create transaction record Transaction transaction = new Transaction( null, userId, amount, "PAYMENT", LocalDateTime.now() ); return transactionRepository.save(transaction); } public BigDecimal getUserBalance(Long userId) { return userRepository.findById(userId) .map(User::getBalance) .orElseThrow(() -> new IllegalArgumentException("User not found")); } } // Test Class @ExtendWith(MockitoExtension.class) class PaymentServiceTest { @Mock private UserRepository userRepository; @Mock private TransactionRepository transactionRepository; private PaymentService paymentService; @BeforeEach void setUp() { paymentService = new PaymentService(userRepository, transactionRepository); } @Test void processPayment_Successful() { // Arrange Long userId = 1L; BigDecimal initialBalance = new BigDecimal("100.00"); BigDecimal paymentAmount = new BigDecimal("50.00"); BigDecimal expectedBalance = new BigDecimal("50.00"); User user = new User(userId, "john_doe", "[email protected]", initialBalance); Transaction expectedTransaction = new Transaction(1L, userId, paymentAmount, "PAYMENT", LocalDateTime.now()); when(userRepository.findById(userId)).thenReturn(Optional.of(user)); when(userRepository.save(any(User.class))).thenAnswer(invocation -> invocation.getArgument(0)); when(transactionRepository.save(any(Transaction.class))).thenReturn(expectedTransaction); // Act Transaction result = paymentService.processPayment(userId, paymentAmount); // Assert assertThat(result).isEqualTo(expectedTransaction); // Verify interactions verify(userRepository).findById(userId); verify(userRepository).save(argThat(userArg -> userArg.getBalance().equals(expectedBalance))); verify(transactionRepository).save(any(Transaction.class)); } @Test void processPayment_UserNotFound() { // Arrange Long userId = 999L; BigDecimal amount = new BigDecimal("50.00"); when(userRepository.findById(userId)).thenReturn(Optional.empty()); // Act & Assert assertThatThrownBy(() -> paymentService.processPayment(userId, amount)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("User not found"); verify(userRepository).findById(userId); verify(userRepository, never()).save(any()); verify(transactionRepository, never()).save(any()); } @Test void processPayment_InsufficientBalance() { // Arrange Long userId = 1L; BigDecimal initialBalance = new BigDecimal("30.00"); BigDecimal paymentAmount = new BigDecimal("50.00"); User user = new User(userId, "john_doe", "[email protected]", initialBalance); when(userRepository.findById(userId)).thenReturn(Optional.of(user)); // Act & Assert assertThatThrownBy(() -> paymentService.processPayment(userId, paymentAmount)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Insufficient balance"); verify(userRepository).findById(userId); verify(userRepository, never()).save(any()); verify(transactionRepository, never()).save(any()); } @Test void getUserBalance_Success() { // Arrange Long userId = 1L; BigDecimal expectedBalance = new BigDecimal("100.00"); User user = new User(userId, "john_doe", "[email protected]", expectedBalance); when(userRepository.findById(userId)).thenReturn(Optional.of(user)); // Act BigDecimal result = paymentService.getUserBalance(userId); // Assert assertThat(result).isEqualByComparingTo(expectedBalance); verify(userRepository).findById(userId); } }

Advanced Mockito Features

Example 3: Argument Captors and Verification

import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.util.Arrays; import java.util.List; import static org.mockito.Mockito.*; import static org.assertj.core.api.Assertions.*; @ExtendWith(MockitoExtension.class) class AdvancedMockitoTest { @Mock private EmailService emailService; @Mock private UserRepository userRepository; @Captor private ArgumentCaptor<String> emailCaptor; @Captor private ArgumentCaptor<User> userCaptor; @Test void testArgumentCaptor() { // Arrange String email = "[email protected]"; // Act emailService.sendEmail(email, "Subject", "Body"); // Assert using argument captor verify(emailService).sendEmail(emailCaptor.capture(), anyString(), anyString()); String capturedEmail = emailCaptor.getValue(); assertThat(capturedEmail).isEqualTo(email); } @Test void testMultipleArgumentCaptures() { // Arrange User user1 = new User(1L, "user1", "[email protected]", new BigDecimal("100.00")); User user2 = new User(2L, "user2", "[email protected]", new BigDecimal("200.00")); // Act userRepository.save(user1); userRepository.save(user2); // Assert - capture multiple invocations verify(userRepository, times(2)).save(userCaptor.capture()); List<User> capturedUsers = userCaptor.getAllValues(); assertThat(capturedUsers).hasSize(2); assertThat(capturedUsers).extracting(User::getUsername) .containsExactly("user1", "user2"); } @Test void testInOrderVerification() { // Arrange UserService userService = new UserService(userRepository, emailService); User user = new User(1L, "john", "[email protected]", new BigDecimal("100.00")); when(userRepository.save(any(User.class))).thenReturn(user); // Act userService.registerUser("john", "[email protected]"); // Assert - verify calls happened in specific order InOrder inOrder = inOrder(userRepository, emailService); inOrder.verify(userRepository).save(any(User.class)); inOrder.verify(emailService).sendWelcomeEmail(anyString()); } } // Supporting classes for the test class EmailService { public void sendEmail(String to, String subject, String body) {} public void sendWelcomeEmail(String email) {} } class UserService { private final UserRepository userRepository; private final EmailService emailService; public UserService(UserRepository userRepository, EmailService emailService) { this.userRepository = userRepository; this.emailService = emailService; } public User registerUser(String username, String email) { User user = new User(null, username, email, BigDecimal.ZERO); User savedUser = userRepository.save(user); emailService.sendWelcomeEmail(email); return savedUser; } }

Example 4: Mocking with Answers and Callbacks

import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import static org.mockito.Mockito.*; import static org.assertj.core.api.Assertions.*; @ExtendWith(MockitoExtension.class) class MockitoAnswersTest { @Mock private IdGenerator idGenerator; @Mock private CacheService cacheService; @Test void testThenAnswer() { // Arrange - use thenAnswer for complex return logic AtomicInteger counter = new AtomicInteger(1); when(idGenerator.generateId()).thenAnswer(invocation -> { int id = counter.getAndIncrement(); return "ID-" + id; }); // Act & Assert assertThat(idGenerator.generateId()).isEqualTo("ID-1"); assertThat(idGenerator.generateId()).isEqualTo("ID-2"); assertThat(idGenerator.generateId()).isEqualTo("ID-3"); } @Test void testThenReturnMultipleValues() { // Arrange - return different values on consecutive calls when(idGenerator.generateId()) .thenReturn("FIRST-ID") .thenReturn("SECOND-ID") .thenThrow(new RuntimeException("No more IDs")); // Act & Assert assertThat(idGenerator.generateId()).isEqualTo("FIRST-ID"); assertThat(idGenerator.generateId()).isEqualTo("SECOND-ID"); assertThatThrownBy(() -> idGenerator.generateId()) .isInstanceOf(RuntimeException.class) .hasMessage("No more IDs"); } @Test void testThenCallRealMethod() { // Arrange - call real method on mock List<String> realList = new ArrayList<>(); List<String> spyList = spy(realList); // Act spyList.add("real"); when(spyList.size()).thenReturn(100); // Stub the method // Assert assertThat(spyList).containsExactly("real"); assertThat(spyList.size()).isEqualTo(100); // Stubbed value } @Test void testDoAnswerForVoidMethods() { // Arrange - use doAnswer for void methods List<String> capturedMessages = new ArrayList<>(); doAnswer(invocation -> { String message = invocation.getArgument(0); capturedMessages.add("Processed: " + message); return null; }).when(cacheService).processMessage(anyString()); // Act cacheService.processMessage("Hello"); cacheService.processMessage("World"); // Assert assertThat(capturedMessages).containsExactly( "Processed: Hello", "Processed: World" ); } } // Supporting classes class IdGenerator { public String generateId() { return "default-id"; } } class CacheService { public void processMessage(String message) { // Default implementation } }

Integration with Spring Boot

Example 5: Spring Boot Service 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 org.springframework.boot.test.context.SpringBootTest; import java.util.List; import java.util.Optional; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static org.assertj.core.api.Assertions.*; // Entity Classes @Entity class Product { @Id private Long id; private String name; private String description; private Double price; private Integer stockQuantity; // Constructors, getters, setters public Product() {} public Product(Long id, String name, String description, Double price, Integer stockQuantity) { this.id = id; this.name = name; this.description = description; this.price = price; this.stockQuantity = stockQuantity; } public Long getId() { return id; } public String getName() { return name; } public String getDescription() { return description; } public Double getPrice() { return price; } public Integer getStockQuantity() { return stockQuantity; } public void setStockQuantity(Integer stockQuantity) { this.stockQuantity = stockQuantity; } } // Repository interface ProductRepository extends JpaRepository<Product, Long> { List<Product> findByPriceLessThan(Double maxPrice); Optional<Product> findByName(String name); } // Service @Service class ProductService { private final ProductRepository productRepository; public ProductService(ProductRepository productRepository) { this.productRepository = productRepository; } public List<Product> getAffordableProducts(Double maxPrice) { return productRepository.findByPriceLessThan(maxPrice); } public Product updateStock(Long productId, Integer newStock) { Product product = productRepository.findById(productId) .orElseThrow(() -> new ProductNotFoundException("Product not found")); product.setStockQuantity(newStock); return productRepository.save(product); } public Product createProduct(Product product) { // Check if product with same name exists if (productRepository.findByName(product.getName()).isPresent()) { throw new DuplicateProductException("Product with this name already exists"); } return productRepository.save(product); } } // Custom Exceptions class ProductNotFoundException extends RuntimeException { public ProductNotFoundException(String message) { super(message); } } class DuplicateProductException extends RuntimeException { public DuplicateProductException(String message) { super(message); } } // Test Class @ExtendWith(MockitoExtension.class) class ProductServiceTest { @Mock private ProductRepository productRepository; @InjectMocks private ProductService productService; @Test void getAffordableProducts_ShouldReturnFilteredProducts() { // Arrange Double maxPrice = 50.0; List<Product> affordableProducts = List.of( new Product(1L, "Product A", "Description A", 25.0, 10), new Product(2L, "Product B", "Description B", 35.0, 5) ); when(productRepository.findByPriceLessThan(maxPrice)).thenReturn(affordableProducts); // Act List<Product> result = productService.getAffordableProducts(maxPrice); // Assert assertThat(result).hasSize(2); assertThat(result).extracting(Product::getPrice) .allMatch(price -> price < maxPrice); verify(productRepository).findByPriceLessThan(maxPrice); } @Test void updateStock_ProductExists_ShouldUpdateStock() { // Arrange Long productId = 1L; Integer newStock = 50; Product existingProduct = new Product(productId, "Product A", "Desc", 25.0, 10); Product updatedProduct = new Product(productId, "Product A", "Desc", 25.0, newStock); when(productRepository.findById(productId)).thenReturn(Optional.of(existingProduct)); when(productRepository.save(any(Product.class))).thenReturn(updatedProduct); // Act Product result = productService.updateStock(productId, newStock); // Assert assertThat(result.getStockQuantity()).isEqualTo(newStock); verify(productRepository).findById(productId); verify(productRepository).save(argThat(product -> product.getStockQuantity().equals(newStock))); } @Test void updateStock_ProductNotFound_ShouldThrowException() { // Arrange Long productId = 999L; when(productRepository.findById(productId)).thenReturn(Optional.empty()); // Act & Assert assertThatThrownBy(() -> productService.updateStock(productId, 50)) .isInstanceOf(ProductNotFoundException.class) .hasMessage("Product not found"); verify(productRepository).findById(productId); verify(productRepository, never()).save(any()); } @Test void createProduct_WithUniqueName_ShouldCreateProduct() { // Arrange Product newProduct = new Product(null, "New Product", "Description", 99.99, 10); Product savedProduct = new Product(1L, "New Product", "Description", 99.99, 10); when(productRepository.findByName("New Product")).thenReturn(Optional.empty()); when(productRepository.save(newProduct)).thenReturn(savedProduct); // Act Product result = productService.createProduct(newProduct); // Assert assertThat(result.getId()).isEqualTo(1L); verify(productRepository).findByName("New Product"); verify(productRepository).save(newProduct); } @Test void createProduct_WithDuplicateName_ShouldThrowException() { // Arrange Product newProduct = new Product(null, "Existing Product", "Description", 99.99, 10); Product existingProduct = new Product(1L, "Existing Product", "Description", 99.99, 10); when(productRepository.findByName("Existing Product")).thenReturn(Optional.of(existingProduct)); // Act & Assert assertThatThrownBy(() -> productService.createProduct(newProduct)) .isInstanceOf(DuplicateProductException.class) .hasMessage("Product with this name already exists"); verify(productRepository).findByName("Existing Product"); verify(productRepository, never()).save(any()); } }

Mockito Best Practices

Example 6: Best Practices and Common Patterns

import org.junit.jupiter.api.BeforeEach; 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.Collections; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static org.assertj.core.api.Assertions.*; @ExtendWith(MockitoExtension.class) class MockitoBestPracticesTest { @Mock private UserRepository userRepository; @Mock private EmailService emailService; private UserService userService; @BeforeEach void setUp() { userService = new UserService(userRepository, emailService); } @Test void shouldUseDescriptiveTestNames() { // Test names should describe the behavior being tested when(userRepository.findAll()).thenReturn(Collections.emptyList()); assertThat(userService.getAllUsers()).isEmpty(); } @Test void shouldVerifyImportantInteractions() { // Only verify interactions that are important for the test User user = new User(1L, "test", "[email protected]", new BigDecimal("100.00")); when(userRepository.save(any(User.class))).thenReturn(user); userService.registerUser("test", "[email protected]"); verify(userRepository).save(any(User.class)); verify(emailService).sendWelcomeEmail("[email protected]"); } @Test void shouldNotOverSpecify() { // Don't verify every single interaction if it's not important User user = new User(1L, "test", "[email protected]", new BigDecimal("100.00")); when(userRepository.save(any(User.class))).thenReturn(user); userService.registerUser("test", "[email protected]"); // Good: Verify important business logic verify(emailService).sendWelcomeEmail("[email protected]"); // Avoid: Over-verifying implementation details // verify(userRepository, times(1)).save(any(User.class)); // verifyNoMoreInteractions(userRepository); } @Test void shouldUseAppropriateArgumentMatchers() { // Use specific matchers when possible when(userRepository.findByName(eq("john"))).thenReturn(Optional.empty()); // Use any() only when the specific value doesn't matter when(userRepository.save(any(User.class))).thenAnswer(inv -> inv.getArgument(0)); userService.registerUser("john", "[email protected]"); verify(userRepository).findByName("john"); } @Test void shouldResetMocksBetweenTestClasses() { // In integration tests, you might need to reset mocks // This is usually handled by the test framework } @Test void shouldAvoidMockingValueObjects() { // Don't mock simple value objects or data classes // Instead, create real instances // Good: Use real objects for simple data carriers User realUser = new User(1L, "john", "[email protected]", new BigDecimal("100.00")); // Avoid: Mocking simple data objects // User mockedUser = mock(User.class); // when(mockedUser.getName()).thenReturn("john"); } } // Additional supporting 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 registerUser(String username, String email) { // Check if user exists userRepository.findByName(username).ifPresent(user -> { throw new IllegalArgumentException("User already exists"); }); User user = new User(null, username, email, BigDecimal.ZERO); User savedUser = userRepository.save(user); emailService.sendWelcomeEmail(email); return savedUser; } public List<User> getAllUsers() { return userRepository.findAll(); } }

Common Mockito Patterns

1. Given-When-Then Pattern

@Test void givenUserExists_whenGetUser_thenReturnUser() { // Given User expectedUser = new User(1L, "john", "[email protected]", new BigDecimal("100.00")); when(userRepository.findById(1L)).thenReturn(Optional.of(expectedUser)); // When User result = userService.getUser(1L); // Then assertThat(result).isEqualTo(expectedUser); }

2. Behavior Verification

@Test void shouldSendEmailAfterUserRegistration() { // When userService.registerUser("john", "[email protected]"); // Then verify(emailService).sendWelcomeEmail("[email protected]"); }

3. Exception Testing

@Test void shouldThrowExceptionWhenUserNotFound() { when(userRepository.findById(999L)).thenReturn(Optional.empty()); assertThatThrownBy(() -> userService.getUser(999L)) .isInstanceOf(UserNotFoundException.class); }

Conclusion

Mockito is an essential tool for Java developers writing unit tests. Key takeaways:

  • Isolate Dependencies: Mock external dependencies to test components in isolation
  • Flexible Stubbing: Configure mock behavior with when().thenReturn() and related methods
  • Verification: Ensure proper interactions with dependencies using verify()
  • Argument Matching: Use argument matchers for flexible verification
  • Best Practices: Follow testing best practices for maintainable, readable tests

Mockito integrates well with JUnit 5 and Spring Boot, making it the go-to choice for mocking in modern Java applications. Remember to use mocks judiciously and focus on testing behavior rather than implementation details.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper