BLOG POSTS
Mockito Mock Void Method – Example Usage

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.

Leave a reply

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