Loading Now

Mockito Tutorial – Basics of Mocking in Java

Mockito Tutorial – Basics of Mocking in Java

Unit testing forms the foundation of any robust Java application. However, managing external dependencies such as databases and web services can complicate your tests significantly. This is where Mockito enters the picture—this widely-used Java mocking framework enables you to create mock objects that perform in precisely the desired manner during testing. This guide will introduce you to the fundamental concepts of Mockito, guide you through its setup process, and provide actionable examples to help you write enhanced, swifter, and more dependable unit tests.

Understanding the Inner Workings of Mockito

Mockito employs a blend of reflection and bytecode manipulation to generate mock objects at runtime. When a mock is instantiated, Mockito creates a proxy class that extends or implements the targeted class/interface. This proxy intercepts function calls, returning specified values or executing user-defined logic based on how you’ve structured your tests.

The framework functions on three core principles:

  • Stubbing – Set what methods return upon invocation
  • Verification – Confirm whether certain methods were called with the anticipated parameters
  • Argument Matching – Utilise adaptable matchers to manage various input conditions

Here’s what occurs when you create a mock:

// Mockito establishes a proxy that intercepts method calls
UserService mockService = Mockito.mock(UserService.class);

// Internally, Mockito:
// 1. Generates a proxy class extending UserService
// 2. Registers the mock within its internal registry
// 3. Ensures default return values (null for objects, 0 for primitives, false for booleans)

Setup and Implementation Guide

Starting with Mockito is quite simple. Add the necessary dependency to your project, and you’re ready to start mocking.

Maven Configuration

Add the following to your pom.xml:


    org.mockito
    mockito-core
    5.5.0
    test




    org.mockito
    mockito-junit-jupiter
    5.5.0
    test

Gradle Configuration

testImplementation 'org.mockito:mockito-core:5.5.0'
testImplementation 'org.mockito:mockito-junit-jupiter:5.5.0'

Basic Mock Creation and Application

Let’s consider a straightforward scenario. Suppose there is a service dependent on a repository:

public class UserService {
    private UserRepository userRepository;
    
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    public User findUserById(Long id) {
        if (id == null || id <= 0) {
            throw new IllegalArgumentException("Invalid user ID");
        }
        return userRepository.findById(id);
    }
    
    public boolean isUserActive(Long id) {
        User user = findUserById(id);
        return user != null && user.isActive();
    }
}

public interface UserRepository {
    User findById(Long id);
}

public class User {
    private Long id;
    private String name;
    private boolean active;
    
    // Constructor, getters, and setters
    public User(Long id, String name, boolean active) {
        this.id = id;
        this.name = name;
        this.active = active;
    }
    
    public boolean isActive() { return active; }
    public Long getId() { return id; }
    public String getName() { return name; }
}

Now, let’s test this service using Mockito:

import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;

public class UserServiceTest {
    
    private UserRepository mockRepository;
    private UserService userService;
    
    @BeforeEach
    void setUp() {
        // Initialize mock repository
        mockRepository = mock(UserRepository.class);
        userService = new UserService(mockRepository);
    }
    
    @Test
    void shouldReturnUserWhenValidIdProvided() {
        // Arrange
        Long userId = 1L;
        User expectedUser = new User(userId, "John Doe", true);
        
        // Set the repository method return
        when(mockRepository.findById(userId)).thenReturn(expectedUser);
        
        // Act
        User actualUser = userService.findUserById(userId);
        
        // Assert
        assertEquals(expectedUser, actualUser);
        verify(mockRepository).findById(userId); // Ensure the method was invoked
    }
    
    @Test
    void shouldThrowExceptionWhenInvalidIdProvided() {
        // Act & Assert
        assertThrows(IllegalArgumentException.class, 
                    () -> userService.findUserById(-1L));
        
        // Ensure repository was not accessed
        verifyInteractions(mockRepository);
    }
    
    @Test
    void shouldReturnTrueWhenUserIsActive() {
        // Arrange
        Long userId = 1L;
        User activeUser = new User(userId, "Jane Doe", true);
        when(mockRepository.findById(userId)).thenReturn(activeUser);
        
        // Act
        boolean isActive = userService.isUserActive(userId);
        
        // Assert
        assertTrue(isActive);
    }
}

Advanced Mocking Strategies

Using Argument Matchers

Mockito features adaptable argument matchers for more responsive stubbing:

import static org.mockito.ArgumentMatchers.*;

@Test
void demonstrateArgumentMatchers() {
    UserRepository mockRepo = mock(UserRepository.class);
    
    // Match any Long value
    when(mockRepo.findById(any(Long.class)))
        .thenReturn(new User(1L, "Default User", true));
    
    // Match specific values
    when(mockRepo.findById(eq(999L))).thenReturn(null);
    
    // Match with custom conditions
    when(mockRepo.findById(longThat(id -> id > 1000)))
        .thenThrow(new RuntimeException("ID too large"));
    
    // Evaluate behaviors
    assertNull(mockRepo.findById(1L));
    assertNull(mockRepo.findById(999L));
    assertThrows(RuntimeException.class, () -> mockRepo.findById(1001L));
}

Verification with Counts and Sequence

@Test
void demonstrateVerificationOptions() {
    UserRepository mockRepo = mock(UserRepository.class);
    UserService service = new UserService(mockRepo);
    
    // Call methods multiple times
    service.findUserById(1L);
    service.findUserById(2L);
    service.findUserById(1L);
    
    // Confirm exact number of calls
    verify(mockRepo, times(2)).findById(1L);
    verify(mockRepo, times(1)).findById(2L);
    
    // Verify minimum/maximum calls
    verify(mockRepo, atLeast(1)).findById(1L);
    verify(mockRepo, atMost(3)).findById(any(Long.class));
    
    // Ensure method was never invoked
    verify(mockRepo, never()).findById(999L);
}

Utilising Annotations for Cleaner Code

import org.mockito.Mock;
import org.mockito.InjectMocks;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
public class UserServiceAnnotationTest {
    
    @Mock
    private UserRepository userRepository;
    
    @InjectMocks
    private UserService userService;
    
    @Test
    void testWithAnnotations() {
        // No need for manual mock creation or injection
        when(userRepository.findById(1L))
            .thenReturn(new User(1L, "Test User", true));
        
        User user = userService.findUserById(1L);
        assertEquals("Test User", user.getName());
    }
}

Practical Scenarios and Applications

Mocking Complex Dependencies

Consider a more intricate example involving multiple dependencies and external service calls:

public class OrderService {
    private PaymentProcessor paymentProcessor;
    private InventoryService inventoryService;
    private EmailService emailService;
    
    public OrderService(PaymentProcessor paymentProcessor, 
                       InventoryService inventoryService,
                       EmailService emailService) {
        this.paymentProcessor = paymentProcessor;
        this.inventoryService = inventoryService;
        this.emailService = emailService;
    }
    
    public OrderResult processOrder(Order order) {
        // Check inventory
        if (!inventoryService.isAvailable(order.getProductId(), order.getQuantity())) {
            return OrderResult.failed("Insufficient inventory");
        }
        
        // Process payment
        PaymentResult paymentResult = paymentProcessor.charge(
            order.getCustomerId(), order.getTotalAmount());
        
        if (!paymentResult.isSuccessful()) {
            return OrderResult.failed("Payment failed: " + paymentResult.getErrorMessage());
        }
        
        // Update inventory
        inventoryService.reserve(order.getProductId(), order.getQuantity());
        
        // Send confirmation email
        emailService.sendOrderConfirmation(order.getCustomerId(), order.getId());
        
        return OrderResult.success(order.getId());
    }
}

Testing this service with various mocks:

@ExtendWith(MockitoExtension.class)
public class OrderServiceTest {
    
    @Mock PaymentProcessor paymentProcessor;
    @Mock InventoryService inventoryService;
    @Mock EmailService emailService;
    @InjectMocks OrderService orderService;
    
    @Test
    void shouldProcessOrderSuccessfully() {
        // Arrange
        Order order = new Order("PROD-1", 2, "CUST-1", new BigDecimal("100.00"));
        
        when(inventoryService.isAvailable("PROD-1", 2)).thenReturn(true);
        when(paymentProcessor.charge("CUST-1", new BigDecimal("100.00")))
            .thenReturn(PaymentResult.success("TXN-123"));
        
        // Act
        OrderResult result = orderService.processOrder(order);
        
        // Assert
        assertTrue(result.isSuccessful());
        
        // Verify all dependencies were called in correct sequence
        InOrder inOrder = inOrder(inventoryService, paymentProcessor, emailService);
        inOrder.verify(inventoryService).isAvailable("PROD-1", 2);
        inOrder.verify(paymentProcessor).charge("CUST-1", new BigDecimal("100.00"));
        inOrder.verify(inventoryService).reserve("PROD-1", 2);
        inOrder.verify(emailService).sendOrderConfirmation("CUST-1", order.getId());
    }
    
    @Test
    void shouldFailWhenInsufficientInventory() {
        // Arrange
        Order order = new Order("PROD-1", 10, "CUST-1", new BigDecimal("500.00"));
        when(inventoryService.isAvailable("PROD-1", 10)).thenReturn(false);
        
        // Act
        OrderResult result = orderService.processOrder(order);
        
        // Assert
        assertFalse(result.isSuccessful());
        assertEquals("Insufficient inventory", result.getErrorMessage());
        
        // Ensure payment was never attempted
        verifyInteractions(paymentProcessor);
        verifyInteractions(emailService);
    }
}

Testing Void Methods and Handling Exceptions

@Test
void shouldHandleEmailServiceFailure() {
    // Arrange
    Order order = new Order("PROD-1", 1, "CUST-1", new BigDecimal("50.00"));
    
    when(inventoryService.isAvailable(anyString(), anyInt())).thenReturn(true);
    when(paymentProcessor.charge(anyString(), any(BigDecimal.class)))
        .thenReturn(PaymentResult.success("TXN-456"));
    
    // Simulate email service throwing exception
    doThrow(new EmailServiceException("SMTP server down"))
        .when(emailService).sendOrderConfirmation(anyString(), anyString());
    
    // Act & Assert
    assertThrows(EmailServiceException.class, () -> orderService.processOrder(order));
    
    // Ensure inventory was still reserved despite email failure
    verify(inventoryService).reserve("PROD-1", 1);
}

Comparisons of Mocking Frameworks

Feature Mockito EasyMock PowerMock WireMock
Learning Curve Easy Moderate Steep Moderate
Static Method Mocking Yes (v3.4+) Yes N/A
Final Class Mocking Yes Yes N/A
Annotation Support Excellent Good Good Limited
HTTP Service Mocking Excellent
Community Support Excellent Good Moderate Good
Performance Fast Fast Slower Fast

Best Practices and Common Errors

Recommended Practices

  • Mock interfaces, not concrete classes – This approach is generally more reliable and manageable
  • Use @Mock annotations – This results in cleaner and more maintainable code compared to manual mock setups
  • Verify behaviour, not just state – Ensure that methods were invoked with correct parameters
  • Keep mocks straightforward – Avoid over-complicating mock configurations
  • Consistently use argument matchers – Be cautious when mixing specific values with matchers
// Good: Clear, uncomplicated stubbing
when(userRepo.findById(1L)).thenReturn(testUser);

// Good: Appropriate use of argument matchers
when(userRepo.findById(any(Long.class))).thenReturn(defaultUser);

// Bad: Incorrectly mixing matchers with specific values
// when(userRepo.findById(eq(1L), "someString")).thenReturn(testUser); // This won't behave as expected

Common Mistakes to Avoid

Excessive Mocking

// Bad: Mocking everything, even trivial objects
@Mock
private String userName; // Avoid mocking simple value objects

@Mock  
private List userNames; // Avoid mocking standard collections

// Good: Limit mocking to external dependencies
@Mock
private UserRepository userRepository;

@Mock
private ExternalApiClient apiClient;

Stubbing Without Verification

// Bad: Stubbing with no verification of interaction
@Test
void badTest() {
    when(mockRepo.findById(1L)).thenReturn(user);
    // Test code that may not even call findById
    assertTrue(someCondition);
    // Missing verification!
}

// Good: Always verify significant interactions
@Test  
void goodTest() {
    when(mockRepo.findById(1L)).thenReturn(user);
    
    User result = userService.getUser(1L);
    
    assertEquals(user, result);
    verify(mockRepo).findById(1L); // Ensure the interaction occurred
}

Ignoring Mock Resets

When reusing mocks across multiple tests, it’s vital to reset them to prevent test contamination:

public class UserServiceTest {
    private UserRepository mockRepo = mock(UserRepository.class);
    
    @BeforeEach
    void setUp() {
        reset(mockRepo); // Clear previous stubbing and interactions
        // Alternatively, use @Mock annotations with MockitoExtension for automatic handling
    }
}

Performance Assessment and Benchmarks

Typically, Mockito offers excellent performance, but here are some metrics to consider:

Operation Time (nanoseconds) Notes
Mock creation ~50,000-100,000 One-time cost per test
Method stubbing ~1,000-5,000 Very fast
Mock method call ~100-500 Minimal overhead
Verification ~1,000-3,000 Varies based on interaction history

Performance Tips

  • Reuse mocks when feasible – Just ensure proper resetting between tests
  • Avoid extensive mock chainswhen(a.getB().getC().getD()).thenReturn(value) can be slow
  • Employ lenient() for frequently called methods – This lessens the overhead for unused stubbing warnings
@Test
void performanceOptimizedTest() {
    UserRepository mockRepo = mock(UserRepository.class);
    
    // Use lenient for methods invoked multiple times
    lenient().when(mockRepo.findById(any(Long.class))).thenReturn(defaultUser);
    lenient().when(mockRepo.existsById(any(Long.class))).thenReturn(true);
    
    // Your test logic here
}

Integration with Testing Frameworks

Spring Boot Integration

Mockito integrates perfectly with Spring Boot testing:

@SpringBootTest
public class UserControllerIntegrationTest {
    
    @MockBean  // Spring Boot annotation that creates and injects mock
    private UserService userService;
    
    @Autowired
    private TestRestTemplate restTemplate;
    
    @Test
    void shouldReturnUserData() {
        // Arrange
        User mockUser = new User(1L, "John Doe", true);
        when(userService.findById(1L)).thenReturn(mockUser);
        
        // Act
        ResponseEntity response = restTemplate.getForEntity("/users/1", User.class);
        
        // Assert
        assertEquals(HttpStatus.OK, response.getStatusCode());
        assertEquals("John Doe", response.getBody().getName());
    }
}

Testcontainers Integration

Sometimes testing against real databases while mocking external services is necessary:

@Testcontainers
@SpringBootTest
public class OrderServiceIntegrationTest {
    
    @Container
    static PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres:13")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test");
    
    @MockBean
    private PaymentService paymentService; // Mock external payment service
    
    @Autowired
    private OrderService orderService;
    
    @Test
    void shouldProcessOrderWithRealDatabase() {
        // Real database, mocked payment service
        when(paymentService.processPayment(any())).thenReturn(PaymentResult.success());
        
        Order order = new Order("PROD-1", 1, "CUST-1", new BigDecimal("99.99"));
        OrderResult result = orderService.processOrder(order);
        
        assertTrue(result.isSuccessful());
    }
}

Troubleshooting Frequently Encountered Issues

Mockito Cannot Mock Final Classes

If you encounter errors regarding final classes in older versions of Mockito:

# Create this file: src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
mock-maker-inline

Alternatively, use the updated dependency that embeds this feature by default:


    org.mockito
    mockito-inline
    5.5.0
    test

Unfinished Stubbing Exception

This occurs when a stubbing call is incomplete:

// Bad: Incomplete stubbing
when(mockRepo.findById(1L)); // Missing thenReturn/thenThrow

// Good: Full stubbing
when(mockRepo.findById(1L)).thenReturn(user);

Issues with ArgumentMatchers

// Bad: Mixing matchers with concrete values
when(service.processUser(eq(1L), "John")).thenReturn(result);

// Good: Use all matchers or all direct values
when(service.processUser(eq(1L), eq("John"))).thenReturn(result);
// OR
when(service.processUser(1L, "John")).thenReturn(result);

Mockito has become the default choice for Java mocking due to its balance of power and ease of use. The techniques discussed here will cover the majority of your testing requirements. For more advanced capabilities such as mocking static methods, creating spy objects, and custom argument matchers, refer to the official Mockito documentation.

Remember, effective mocking focuses on testing behaviour rather than implementation details. Concentrate on confirming that your code accurately calls the appropriate dependencies with the correct parameters, and your tests will be both resilient and maintainable.



This article includes information and content from various online sources. We recognise and appreciate the contributions of all original authors, publishers, and websites. While every effort has been made to properly attribute the source material, any unintentional oversights or omissions do not signify a copyright infringement. All brand names, logos, and images mentioned are the property of their respective owners. Should you believe any content used in this article violates your copyright, please contact us immediately for review and swift action.

This article is purely for informational and educational purposes and does not infringe on copyright owner’s rights. If any protected material has been used without correct attribution or in violation of copyright law, it is unintentional and we will promptly rectify it upon notification. Please note that the republication, redistribution, or reproduction of any part or the entirety of the contents in any form is prohibited without explicit written permission from the author and website owner. For permissions or further inquiries, please get in touch with us.