
Mockito Mock Void Method – Example Usage
Mockito is a powerful mocking framework for Java that allows developers to create and verify mock objects in unit tests. When dealing with void methods (methods that don’t return values), many developers struggle with proper mocking techniques. This guide covers how to effectively mock void methods using Mockito, including handling method calls that perform side effects, throwing exceptions from void methods, and verifying interactions. You’ll learn practical approaches to test methods that call void dependencies, common patterns for handling different scenarios, and troubleshooting techniques when your mocks don’t behave as expected.
Understanding Void Method Mocking in Mockito
Void methods present unique challenges in unit testing because they don’t return values to assert against. Instead, they typically perform side effects like updating databases, sending emails, or modifying object state. Mockito provides several approaches to handle these scenarios:
- doNothing() – Default behavior for void methods, explicitly states no action
- doThrow() – Makes the void method throw an exception
- doAnswer() – Provides custom behavior with access to method arguments
- doCallRealMethod() – Calls the actual implementation instead of mocking
The key difference from regular method mocking is the syntax. Instead of using when(mock.method()).thenReturn(value)
, void methods use the do*().when(mock).method()
pattern.
Basic Void Method Mocking Implementation
Let’s start with a simple example. Consider a service that processes user data and calls a void method to audit the operation:
public class UserService {
private AuditLogger auditLogger;
private UserRepository userRepository;
public UserService(AuditLogger auditLogger, UserRepository userRepository) {
this.auditLogger = auditLogger;
this.userRepository = userRepository;
}
public User updateUser(Long userId, String newEmail) {
User user = userRepository.findById(userId);
user.setEmail(newEmail);
userRepository.save(user);
// This is a void method we need to mock
auditLogger.logUserUpdate(userId, newEmail);
return user;
}
}
public interface AuditLogger {
void logUserUpdate(Long userId, String newEmail);
void logUserDeletion(Long userId);
}
Here’s how to write a comprehensive test that mocks the void method:
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private AuditLogger auditLogger;
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void shouldUpdateUserAndLogAudit() {
// Arrange
Long userId = 1L;
String newEmail = "newemail@example.com";
User existingUser = new User(userId, "oldemail@example.com");
when(userRepository.findById(userId)).thenReturn(existingUser);
when(userRepository.save(any(User.class))).thenReturn(existingUser);
// Mock void method - doNothing() is actually default behavior
doNothing().when(auditLogger).logUserUpdate(userId, newEmail);
// Act
User result = userService.updateUser(userId, newEmail);
// Assert
assertEquals(newEmail, result.getEmail());
// Verify the void method was called with correct parameters
verify(auditLogger).logUserUpdate(userId, newEmail);
verify(auditLogger, times(1)).logUserUpdate(anyLong(), anyString());
}
}
Advanced Void Method Mocking Scenarios
Making Void Methods Throw Exceptions
Testing error handling is crucial. Here’s how to make void methods throw exceptions:
@Test
void shouldHandleAuditLoggingFailure() {
// Arrange
Long userId = 1L;
String newEmail = "test@example.com";
User existingUser = new User(userId, "old@example.com");
when(userRepository.findById(userId)).thenReturn(existingUser);
when(userRepository.save(any(User.class))).thenReturn(existingUser);
// Make the void method throw an exception
doThrow(new RuntimeException("Audit service unavailable"))
.when(auditLogger).logUserUpdate(userId, newEmail);
// Act & Assert
assertThrows(RuntimeException.class, () -> {
userService.updateUser(userId, newEmail);
});
// Verify the method was called before exception
verify(auditLogger).logUserUpdate(userId, newEmail);
}
Using doAnswer() for Complex Behavior
When you need custom logic in your void method mock, use doAnswer()
:
@Test
void shouldCaptureAuditParameters() {
// Arrange
Long userId = 1L;
String newEmail = "test@example.com";
User existingUser = new User(userId, "old@example.com");
List<String> capturedLogs = new ArrayList<>();
when(userRepository.findById(userId)).thenReturn(existingUser);
when(userRepository.save(any(User.class))).thenReturn(existingUser);
// Custom behavior for void method
doAnswer(invocation -> {
Long capturedUserId = invocation.getArgument(0);
String capturedEmail = invocation.getArgument(1);
capturedLogs.add("User " + capturedUserId + " updated to " + capturedEmail);
return null; // void methods return null
}).when(auditLogger).logUserUpdate(anyLong(), anyString());
// Act
userService.updateUser(userId, newEmail);
// Assert
assertEquals(1, capturedLogs.size());
assertTrue(capturedLogs.get(0).contains(userId.toString()));
assertTrue(capturedLogs.get(0).contains(newEmail));
}
Common Patterns and Use Cases
Multiple Void Method Calls
When your method calls multiple void methods, you can set up different behaviors for each:
public class NotificationService {
private EmailSender emailSender;
private SmsSender smsSender;
private PushNotificationSender pushSender;
public void sendWelcomeNotifications(User user) {
emailSender.sendWelcomeEmail(user.getEmail());
smsSender.sendWelcomeSms(user.getPhoneNumber());
pushSender.sendWelcomePush(user.getDeviceToken());
}
}
@Test
void shouldSendAllWelcomeNotifications() {
// Arrange
User user = new User("test@example.com", "+1234567890", "device123");
// Different behaviors for different void methods
doNothing().when(emailSender).sendWelcomeEmail(anyString());
doThrow(new SmsException("SMS service down"))
.when(smsSender).sendWelcomeSms(anyString());
doNothing().when(pushSender).sendWelcomePush(anyString());
// Act & Assert
assertThrows(SmsException.class, () -> {
notificationService.sendWelcomeNotifications(user);
});
// Verify call order and interactions
InOrder inOrder = inOrder(emailSender, smsSender, pushSender);
inOrder.verify(emailSender).sendWelcomeEmail(user.getEmail());
inOrder.verify(smsSender).sendWelcomeSms(user.getPhoneNumber());
// pushSender should not be called due to SMS exception
verify(pushSender, never()).sendWelcomePush(anyString());
}
Conditional Void Method Mocking
Sometimes you want different behavior based on method arguments:
@Test
void shouldHandleDifferentUserTypes() {
// Mock different behavior based on arguments
doNothing().when(auditLogger).logUserUpdate(eq(1L), anyString());
doThrow(new SecurityException("Admin user modification"))
.when(auditLogger).logUserUpdate(eq(999L), anyString());
// Test regular user - should succeed
assertDoesNotThrow(() -> {
auditLogger.logUserUpdate(1L, "regular@example.com");
});
// Test admin user - should throw exception
assertThrows(SecurityException.class, () -> {
auditLogger.logUserUpdate(999L, "admin@example.com");
});
}
Verification Techniques and Best Practices
Proper verification is essential when testing void methods. Here are key techniques:
Verification Type | Method | Use Case | Example |
---|---|---|---|
Basic verification | verify(mock).method() | Ensure method was called once | verify(logger).log(“message”) |
Never called | verify(mock, never()) | Ensure method was not called | verify(emailSender, never()).send() |
Multiple calls | verify(mock, times(n)) | Verify exact number of calls | verify(cache, times(3)).clear() |
At least/most | atLeast(), atMost() | Flexible call count verification | verify(logger, atLeast(1)).debug(any()) |
Call order | InOrder.verify() | Verify method call sequence | inOrder.verify(service).validate() |
Advanced Verification Example
@Test
void shouldVerifyComplexInteractions() {
// Arrange
User user = new User(1L, "test@example.com");
// Act
userService.processUser(user);
// Verify with argument matchers
verify(auditLogger).logUserUpdate(
eq(1L),
argThat(email -> email.contains("@example.com"))
);
// Verify no unexpected interactions
verifyNoMoreInteractions(auditLogger);
// Capture arguments for detailed verification
ArgumentCaptor<Long> userIdCaptor = ArgumentCaptor.forClass(Long.class);
ArgumentCaptor<String> emailCaptor = ArgumentCaptor.forClass(String.class);
verify(auditLogger).logUserUpdate(userIdCaptor.capture(), emailCaptor.capture());
assertEquals(1L, userIdCaptor.getValue());
assertTrue(emailCaptor.getValue().endsWith("@example.com"));
}
Troubleshooting Common Issues
Stubbing Exception on Void Method
A common mistake is trying to use when().thenThrow()
syntax with void methods:
// ❌ Wrong - This will cause UnnecessaryStubbingException
when(mockService.voidMethod()).thenThrow(new RuntimeException());
// ✅ Correct - Use doThrow() for void methods
doThrow(new RuntimeException()).when(mockService).voidMethod();
Verification Failures
When verification fails, check these common issues:
- Argument mismatch – Use
any()
matchers or exact values - Mock not injected – Ensure
@InjectMocks
is properly configured - Method not called – Verify your test actually exercises the code path
- Wrong mock instance – Make sure you’re verifying the correct mock
@Test
void debugVerificationIssues() {
// Enable verbose verification for debugging
MockitoAnnotations.openMocks(this);
// Use argument capture to see actual values
ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
// Your test code here...
verify(logger).log(messageCaptor.capture());
System.out.println("Captured message: " + messageCaptor.getValue());
// Alternative: use lenient() for problematic stubs
lenient().doNothing().when(problematicMock).troublesomeMethod();
}
Performance Considerations and Integration
When working with void method mocks in larger applications, consider these performance aspects:
- Mock creation overhead – Reuse mocks when possible, especially in integration tests
- Verification complexity – Complex argument matchers can slow down test execution
- Memory usage – ArgumentCaptor instances hold references to captured arguments
For applications deployed on VPS environments, consider running your test suites in parallel to reduce CI/CD pipeline times. When using dedicated servers for development, you can leverage more aggressive parallel testing strategies.
Integration with Spring Boot
In Spring Boot applications, combine Mockito with @MockBean
for integration testing:
@SpringBootTest
class UserServiceIntegrationTest {
@MockBean
private AuditLogger auditLogger;
@Autowired
private UserService userService;
@Test
void shouldIntegrateWithSpringContext() {
doNothing().when(auditLogger).logUserUpdate(anyLong(), anyString());
User result = userService.updateUser(1L, "integration@test.com");
assertNotNull(result);
verify(auditLogger).logUserUpdate(1L, "integration@test.com");
}
}
This approach is particularly useful when testing services that interact with external systems or require specific application context configuration. For more advanced testing patterns and Mockito documentation, refer to the official Mockito API documentation.
Remember that effective void method mocking is about verifying behavior and interactions rather than return values. Focus on ensuring your code calls the right methods with correct parameters, handles exceptions appropriately, and maintains proper call sequences when order matters.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.