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 
@InjectMocksis 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.