BLOG POSTS
Mockito Tutorial – Basics of Mocking in Java

Mockito Tutorial – Basics of Mocking in Java

Unit testing is the backbone of any solid Java application, but dealing with external dependencies like databases, web services, or complex object hierarchies can turn your tests into a nightmare. That’s where Mockito comes in – it’s the most popular mocking framework for Java that lets you create fake objects (mocks) that behave exactly how you want them to during testing. This tutorial will walk you through Mockito’s core concepts, show you how to set it up, and give you practical examples that you can start using immediately to write better, faster, and more reliable unit tests.

How Mockito Works Under the Hood

Mockito uses a combination of reflection and bytecode manipulation to create mock objects at runtime. When you create a mock, Mockito generates a proxy class that extends or implements your target class/interface. This proxy intercepts method calls and returns predefined values or executes custom logic based on your test setup.

The framework operates on three main principles:

  • Stubbing – Define what methods should return when called
  • Verification – Check if specific methods were called with expected parameters
  • Argument matching – Use flexible matchers to handle different input scenarios

Here’s what happens when you create a mock:

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

// Behind the scenes, Mockito:
// 1. Creates a proxy class extending UserService
// 2. Registers the mock in its internal registry
// 3. Sets up default return values (null for objects, 0 for primitives, false for booleans)

Step-by-Step Setup and Implementation

Getting started with Mockito is straightforward. Add the dependency to your project and you’re ready to mock.

Maven Setup

Add this to your pom.xml:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>5.5.0</version>
    <scope>test</scope>
</dependency>

<!-- For JUnit 5 integration -->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-junit-jupiter</artifactId>
    <version>5.5.0</version>
    <scope>test</scope>
</dependency>

Gradle Setup

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

Basic Mock Creation and Usage

Let’s start with a simple example. Imagine you have a service that depends 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;
    
    // constructors, getters, 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() {
        // Create 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);
        
        // Stub the repository method
        when(mockRepository.findById(userId)).thenReturn(expectedUser);
        
        // Act
        User actualUser = userService.findUserById(userId);
        
        // Assert
        assertEquals(expectedUser, actualUser);
        verify(mockRepository).findById(userId); // Verify the method was called
    }
    
    @Test
    void shouldThrowExceptionWhenInvalidIdProvided() {
        // Act & Assert
        assertThrows(IllegalArgumentException.class, 
                    () -> userService.findUserById(-1L));
        
        // Verify repository was never called
        verifyNoInteractions(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 Techniques

Argument Matchers

Mockito provides flexible argument matchers for more dynamic 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"));
    
    // Test the behaviors
    assertNotNull(mockRepo.findById(1L));
    assertNull(mockRepo.findById(999L));
    assertThrows(RuntimeException.class, () -> mockRepo.findById(1001L));
}

Verification with Times and Order

@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);
    
    // Verify exact number of calls
    verify(mockRepo, times(2)).findById(1L);
    verify(mockRepo, times(1)).findById(2L);
    
    // Verify at least/most calls
    verify(mockRepo, atLeast(1)).findById(1L);
    verify(mockRepo, atMost(3)).findById(any(Long.class));
    
    // Verify never called
    verify(mockRepo, never()).findById(999L);
}

Using 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());
    }
}

Real-World Examples and Use Cases

Mocking Complex Dependencies

Here's a more realistic example with 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 multiple 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 order
        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());
        
        // Verify payment was never attempted
        verifyNoInteractions(paymentProcessor);
        verifyNoInteractions(emailService);
    }
}

Testing Void Methods and 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"));
    
    // Make email service throw exception
    doThrow(new EmailServiceException("SMTP server down"))
        .when(emailService).sendOrderConfirmation(anyString(), anyString());
    
    // Act & Assert
    assertThrows(EmailServiceException.class, () -> orderService.processOrder(order));
    
    // Verify inventory was still reserved despite email failure
    verify(inventoryService).reserve("PROD-1", 1);
}

Framework Comparisons

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

Best Practices and Common Pitfalls

Do's

  • Mock interfaces, not concrete classes – It's easier and more reliable
  • Use @Mock annotations – Cleaner and more maintainable than manual mock creation
  • Verify behavior, not just state – Check that methods were called with correct parameters
  • Keep mocks simple – Don't over-complicate your mock setups
  • Use argument matchers consistently – Mix specific values and matchers carefully
// Good: Simple, clear stubbing
when(userRepo.findById(1L)).thenReturn(testUser);

// Good: Using argument matchers appropriately  
when(userRepo.findById(any(Long.class))).thenReturn(defaultUser);

// Bad: Mixing matchers and specific values incorrectly
// when(userRepo.findById(eq(1L), "someString")).thenReturn(testUser); // Won't work as expected

Common Pitfalls to Avoid

Over-Mocking

// Bad: Mocking everything including simple objects
@Mock
private String userName; // Don't mock simple value objects

@Mock  
private List<String> userNames; // Don't mock standard collections

// Good: Mock only external dependencies
@Mock
private UserRepository userRepository;

@Mock
private ExternalApiClient apiClient;

Stubbing Without Verification

// Bad: Stubbing without verifying the interaction
@Test
void badTest() {
    when(mockRepo.findById(1L)).thenReturn(user);
    // Test code that might not even call findById
    assertTrue(someCondition);
    // No verification!
}

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

Ignoring Mock Reset

When using mocks across multiple tests, reset them to avoid test pollution:

public class UserServiceTest {
    private UserRepository mockRepo = mock(UserRepository.class);
    
    @BeforeEach
    void setUp() {
        reset(mockRepo); // Clear previous stubbing and interactions
        // Or use @Mock annotations with MockitoExtension which handles this automatically
    }
}

Performance Considerations and Benchmarks

Mockito's performance is generally excellent, but here are some numbers to keep in mind:

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 Depends on interaction history

Performance Tips

  • Reuse mocks when possible – But ensure proper reset between tests
  • Avoid deep mock chainswhen(a.getB().getC().getD()).thenReturn(value) is slow
  • Use lenient() for frequently called methods – Reduces overhead for unused stubbing warnings
@Test
void performanceOptimizedTest() {
    UserRepository mockRepo = mock(UserRepository.class);
    
    // Use lenient for methods called 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 works seamlessly 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<User> response = restTemplate.getForEntity("/users/1", User.class);
        
        // Assert
        assertEquals(HttpStatus.OK, response.getStatusCode());
        assertEquals("John Doe", response.getBody().getName());
    }
}

Testcontainers Integration

Sometimes you want to test against real databases but mock external services:

@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 Common Issues

Mockito Cannot Mock Final Classes

If you're getting errors about final classes in older Mockito versions:

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

Or use the newer dependency that includes this by default:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>5.5.0</version>
    <scope>test</scope>
</dependency>

UnfinishedStubbingException

This happens when you don't complete a stubbing call:

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

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

ArgumentMatchers Issues

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

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

Mockito has become the de-facto standard for Java mocking because it strikes the perfect balance between power and simplicity. The techniques covered here will handle 95% of your testing scenarios. For more advanced features like static method mocking, spy objects, and custom argument matchers, check out the official Mockito documentation.

Remember, good mocking is about testing behavior, not implementation details. Focus on verifying that your code calls the right dependencies with the right parameters, and you'll end up with tests that are both robust and maintainable.



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