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 chains –
when(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.