
Mockito Mock Examples – Unit Testing in Java
Unit testing is a cornerstone of robust Java development, but testing code that depends on external services, databases, or complex objects can be challenging. Mockito simplifies this by allowing you to create mock objects that simulate real dependencies, making your tests faster, more reliable, and focused on the specific functionality you’re testing. This guide walks through practical Mockito mock examples, from basic mock creation to advanced testing scenarios, giving you the tools to write comprehensive unit tests that actually work in production environments.
Understanding Mockito’s Core Concepts
Mockito works by creating proxy objects that mimic the behavior of real dependencies. Instead of calling actual methods that might hit a database or external API, your tests interact with controlled mock objects that return predetermined responses. This approach isolates the unit under test and eliminates external dependencies that could cause flaky or slow tests.
The framework provides three main types of test doubles:
- Mocks – Objects that verify interactions and can return stubbed responses
- Spies – Partial mocks that wrap real objects while allowing selective method stubbing
- Stubs – Objects that only provide predetermined responses without interaction verification
To get started, add Mockito to your project dependencies:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
Basic Mock Creation and Configuration
The simplest way to create mocks is using the @Mock
annotation combined with MockitoExtension
. Here’s a practical example testing a user service that depends on a repository:
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void shouldReturnUserWhenFound() {
// Given
Long userId = 1L;
User expectedUser = new User(userId, "john@example.com", "John Doe");
when(userRepository.findById(userId)).thenReturn(Optional.of(expectedUser));
// When
User actualUser = userService.getUser(userId);
// Then
assertThat(actualUser.getEmail()).isEqualTo("john@example.com");
verify(userRepository).findById(userId);
}
}
The @InjectMocks
annotation automatically injects the mocked dependencies into your service under test. For scenarios where you need more control, create mocks programmatically:
@Test
void shouldHandleRepositoryException() {
UserRepository mockRepository = mock(UserRepository.class);
UserService service = new UserService(mockRepository);
when(mockRepository.findById(any()))
.thenThrow(new DatabaseException("Connection failed"));
assertThrows(ServiceException.class, () -> service.getUser(1L));
}
Advanced Stubbing Techniques
Real-world scenarios often require more sophisticated mock behavior. Mockito provides several advanced stubbing options that handle complex interactions:
@Test
void shouldDemonstrateAdvancedStubbing() {
// Sequential return values
when(userRepository.findById(1L))
.thenReturn(Optional.of(new User(1L, "first@call.com", "First")))
.thenReturn(Optional.of(new User(1L, "second@call.com", "Second")));
// Argument matching with custom logic
when(userRepository.findByEmail(argThat(email -> email.endsWith("@admin.com"))))
.thenReturn(Optional.of(new User(999L, "admin@admin.com", "Admin")));
// Answer with custom logic
when(userRepository.save(any(User.class))).thenAnswer(invocation -> {
User user = invocation.getArgument(0);
user.setId(System.currentTimeMillis()); // Simulate ID generation
return user;
});
// Void method stubbing
doThrow(new ValidationException("Invalid data"))
.when(userRepository).deleteById(eq(-1L));
}
For asynchronous operations, mock CompletableFuture returns:
@Test
void shouldHandleAsyncOperations() {
AsyncUserService asyncService = mock(AsyncUserService.class);
User user = new User(1L, "async@test.com", "Async User");
when(asyncService.findUserAsync(1L))
.thenReturn(CompletableFuture.completedFuture(user));
CompletableFuture<User> result = asyncService.findUserAsync(1L);
assertThat(result.join().getEmail()).isEqualTo("async@test.com");
}
Spy Objects for Partial Mocking
Spies are particularly useful when you want to test real object behavior while stubbing specific methods. This is common when testing legacy code or integration scenarios:
@Test
void shouldUseSpyForPartialMocking() {
UserService realService = new UserService(userRepository);
UserService spyService = spy(realService);
// Use real method for validation
// Stub only the external dependency call
doReturn(Optional.of(new User(1L, "spy@test.com", "Spy User")))
.when(userRepository).findById(1L);
// This calls the real validation logic but uses mocked repository
User result = spyService.getUser(1L);
assertThat(result.getEmail()).isEqualTo("spy@test.com");
verify(spyService).getUser(1L); // Verify the actual method was called
}
Be cautious with spies – they can lead to unexpected behavior if the real methods have side effects:
@Test
void shouldAvoidSpyPitfalls() {
List<String> spyList = spy(new ArrayList<>());
// Wrong - this calls the real add() method first
// when(spyList.get(0)).thenReturn("mocked");
// Correct - use doReturn for spies
doReturn("mocked").when(spyList).get(0);
assertThat(spyList.get(0)).isEqualTo("mocked");
}
Verification Patterns and Argument Capturing
Mockito’s verification capabilities go beyond simple method call checks. You can verify complex interactions and capture arguments for detailed assertions:
@Test
void shouldVerifyComplexInteractions() {
// Test service that sends notifications
NotificationService notificationService = mock(NotificationService.class);
UserService userService = new UserService(userRepository, notificationService);
User newUser = new User(null, "new@user.com", "New User");
when(userRepository.save(any(User.class)))
.thenReturn(new User(1L, "new@user.com", "New User"));
userService.createUser(newUser);
// Verify notification was sent with correct content
ArgumentCaptor<NotificationMessage> messageCaptor =
ArgumentCaptor.forClass(NotificationMessage.class);
verify(notificationService).sendNotification(messageCaptor.capture());
NotificationMessage capturedMessage = messageCaptor.getValue();
assertThat(capturedMessage.getRecipient()).isEqualTo("new@user.com");
assertThat(capturedMessage.getSubject()).contains("Welcome");
// Verify method call order
InOrder inOrder = inOrder(userRepository, notificationService);
inOrder.verify(userRepository).save(any(User.class));
inOrder.verify(notificationService).sendNotification(any());
}
For timeout-sensitive operations, use verification with timing constraints:
@Test
void shouldVerifyAsyncCalls() {
AsyncProcessor processor = mock(AsyncProcessor.class);
processor.processAsync("test-data");
// Verify the method was called within 2 seconds
verify(processor, timeout(2000)).processAsync("test-data");
// Verify it was called at least 3 times within 5 seconds
verify(processor, timeout(5000).atLeast(3)).processAsync(anyString());
}
Testing REST Controllers and Web Layers
MockMvc integrates seamlessly with Mockito for testing web layers. Here’s a complete controller test example:
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void shouldReturnUserJson() throws Exception {
User user = new User(1L, "test@example.com", "Test User");
when(userService.getUser(1L)).thenReturn(user);
mockMvc.perform(get("/api/users/1")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.email").value("test@example.com"))
.andExpect(jsonPath("$.name").value("Test User"));
verify(userService).getUser(1L);
}
@Test
void shouldHandleValidationErrors() throws Exception {
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"email\":\"invalid-email\"}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.errors").exists());
verify(userService, never()).createUser(any());
}
}
Performance Considerations and Best Practices
Mock creation and verification can impact test performance, especially in large test suites. Here are optimization strategies:
Practice | Impact | Implementation |
---|---|---|
Use @MockBean sparingly | High | Creates new Spring context, slowing tests significantly |
Reset mocks between tests | Medium | Use @ExtendWith(MockitoExtension.class) for automatic reset |
Avoid over-verification | Medium | Verify only behavior that matters to your test case |
Use lenient() for strict stubbing | Low | Prevents unnecessary stubbing exceptions in complex setups |
Here’s a performance-optimized test setup:
@ExtendWith(MockitoExtension.class)
class OptimizedUserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private NotificationService notificationService;
private UserService userService;
@BeforeEach
void setUp() {
// Initialize once per test method
userService = new UserService(userRepository, notificationService);
// Common stubbing for most tests
lenient().when(notificationService.isEnabled()).thenReturn(true);
}
@Test
void shouldCreateUserEfficiently() {
// Specific stubbing only when needed
when(userRepository.save(any())).thenReturn(new User(1L, "test", "test"));
User result = userService.createUser(new User(null, "test", "test"));
// Verify only critical interactions
assertThat(result.getId()).isEqualTo(1L);
}
}
Common Pitfalls and Troubleshooting
Several issues frequently trip up developers when working with Mockito. Here are the most common problems and their solutions:
Stubbing void methods incorrectly:
// Wrong - this won't compile
// when(mockService.deleteUser(1L)).thenThrow(new RuntimeException());
// Correct
doThrow(new RuntimeException()).when(mockService).deleteUser(1L);
// For void methods that should do nothing
doNothing().when(mockService).logActivity(any());
Final class mocking issues:
Mockito 2+ supports final class mocking, but you need to enable it:
# Create src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
mock-maker-inline
Static method mocking (Mockito 3.4+):
@Test
void shouldMockStaticMethods() {
try (MockedStatic<LocalDateTime> mockedStatic = mockStatic(LocalDateTime.class)) {
LocalDateTime fixedTime = LocalDateTime.of(2023, 1, 1, 12, 0);
mockedStatic.when(LocalDateTime::now).thenReturn(fixedTime);
// Your test code that calls LocalDateTime.now()
String result = service.generateTimestamp();
assertThat(result).contains("2023-01-01");
} // Static mock automatically closed
}
Argument matcher mixing:
// Wrong - mixing matchers with actual values
// when(service.findUser(eq(1L), "john")).thenReturn(user);
// Correct - use matchers for all arguments or none
when(service.findUser(eq(1L), eq("john"))).thenReturn(user);
// Or use actual values for all
when(service.findUser(1L, "john")).thenReturn(user);
Integration with Testing Frameworks
Mockito works well with various testing frameworks and CI/CD pipelines. For JUnit 5 integration, ensure you’re using the correct extension:
@ExtendWith(MockitoExtension.class)
class JUnit5MockitoTest {
// Test implementation
}
// For parameterized tests with mocks
@ExtendWith(MockitoExtension.class)
@ParameterizedTest
@ValueSource(strings = {"admin@test.com", "user@test.com"})
void shouldHandleMultipleEmails(String email, @Mock UserRepository repository) {
when(repository.findByEmail(email)).thenReturn(Optional.of(new User()));
// Test logic
}
For Spring Boot integration tests, consider your server infrastructure needs. Whether you’re running tests on a VPS or dedicated servers, ensure your CI/CD environment has sufficient resources for comprehensive test suites that include both unit and integration tests.
TestContainers work excellently alongside Mockito for hybrid testing approaches:
@Testcontainers
class HybridIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:13")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
@Mock
private ExternalApiService externalApiService; // Mock external dependencies
@Autowired
private UserRepository userRepository; // Real database interaction
@Test
void shouldTestWithRealDatabaseAndMockedExternals() {
// Use real database for data operations
User savedUser = userRepository.save(new User("real@test.com", "Real User"));
// Mock external API calls
when(externalApiService.validateEmail(any())).thenReturn(true);
// Test your service with mixed real and mocked dependencies
assertThat(savedUser.getId()).isNotNull();
}
}
The official Mockito documentation provides comprehensive API reference and advanced usage patterns. For teams adopting test-driven development, Mockito’s mock-first approach encourages better API design by forcing you to think about interfaces and dependencies before implementation.
These examples demonstrate Mockito’s flexibility in creating maintainable, fast-running unit tests that provide confidence in your code’s behavior without the complexity and fragility of integration tests for every scenario.

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.