Mocking in Testing
Learn how to use fake objects (mocks) to test your code in isolation
What is Mocking?
Using pretend objects to test your code
🎭 The Toy Phone Analogy
Imagine you're a kid playing 'restaurant'. You don't need a REAL kitchen to pretend to cook! You use toy food, toy plates, and toy utensils. They look like the real thing, but they're just pretend.
Mocking is the same idea! Instead of using REAL databases, REAL APIs, or REAL payment systems in your tests, you use 'toy' versions (mocks) that behave like the real thing.
📱 Real Example: Testing a Phone Call
Let's say you're testing an app that makes phone calls. Do you want to:
- ❌ Actually call real phone numbers every time you test? (Expensive!)
- ❌ Wait for someone to answer? (Slow!)
- ❌ Depend on phone networks working? (Unreliable!)
✅ OR... use a mock phone object that pretends to make calls? (Fast, free, reliable!)
Why Do We Need Mocking?
Testing shouldn't depend on external systems
🚀 Speed
Real databases and APIs are slow. Mocks are instant! Your tests can run in milliseconds instead of seconds.
🎯 Isolation
You're testing YOUR code, not someone else's database or API. Mocks let you focus on one thing at a time.
💰 Cost
Some APIs charge money per request! Imagine paying $0.01 every time you run your tests. With mocks: FREE!
🧪 Control
You can make mocks return any value you want. Test error cases without breaking real systems!
Mockito - The Mocking Framework
The most popular mocking library for Java
Mockito is a Java library that makes creating mock objects super easy. It's like having a toy factory that can create any toy you need for testing!
Step 1: Add Mockito to Your Project
First, add Mockito dependency (Maven):
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>5.8.0</version> <scope>test</scope></dependency><!-- Optional but recommended: Mockito with JUnit 5 --><dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <version>5.8.0</version> <scope>test</scope></dependency>Step 2: Your First Mock
Let's test a UserService that depends on a UserRepository (database):
// UserRepository.java - Talks to database (we'll mock this!)public interface UserRepository { User findById(Long id); void save(User user); List<User> findAll();}// User.java - Simple data classpublic class User { private Long id; private String name; private String email; // Constructor, getters, setters...}// UserService.java - The class we want to testpublic class UserService { private UserRepository repository; public UserService(UserRepository repository) { this.repository = repository; } public User getUserById(Long id) { return repository.findById(id); } public boolean isEmailValid(User user) { return user.getEmail() != null && user.getEmail().contains("@"); } public void createUser(User user) { if (!isEmailValid(user)) { throw new IllegalArgumentException("Invalid email!"); } repository.save(user); }}Now let's test UserService using a MOCK repository:
import org.junit.jupiter.api.Test;import org.junit.jupiter.api.extension.ExtendWith;import org.mockito.Mock;import org.mockito.junit.jupiter.MockitoExtension;import static org.mockito.Mockito.*;import static org.junit.jupiter.api.Assertions.*;@ExtendWith(MockitoExtension.class) // Tells JUnit to use Mockitoclass UserServiceTest { @Mock // This creates a fake UserRepository! private UserRepository mockRepository; @Test void testGetUserById() { // Step 1: Create the service with our mock UserService service = new UserService(mockRepository); // Step 2: Tell the mock what to return User fakeUser = new User(1L, "John", "john@example.com"); when(mockRepository.findById(1L)).thenReturn(fakeUser); // Step 3: Call the method we're testing User result = service.getUserById(1L); // Step 4: Check if it works! assertEquals("John", result.getName()); assertEquals("john@example.com", result.getEmail()); // Step 5: Verify the mock was called correctly verify(mockRepository).findById(1L); }}🎓 Let's Understand This Code:
@MockCreates a fake UserRepository. It looks real but doesn't actually connect to a database!
when(...).thenReturn(...)This is called 'stubbing'. You're teaching the mock: 'When someone calls this method, return this value.' Like programming a robot!
verify(...)Checks if the mock was called. It's like asking: 'Did anyone actually use this toy?' This ensures your code called the repository!
Step 3: Testing Different Scenarios
@ExtendWith(MockitoExtension.class)class UserServiceAdvancedTest { @Mock private UserRepository mockRepository; private UserService service; @BeforeEach void setUp() { service = new UserService(mockRepository); } @Test void testCreateUserWithValidEmail() { // Create a user with valid email User user = new User(1L, "Jane", "jane@example.com"); // We don't need to stub anything because save() returns void // Test it! service.createUser(user); // Verify save was called with our user verify(mockRepository).save(user); } @Test void testCreateUserWithInvalidEmail_ThrowsException() { // Create a user with INVALID email (no @) User user = new User(1L, "Bob", "invalid-email"); // This should throw an exception! assertThrows(IllegalArgumentException.class, () -> { service.createUser(user); }); // Verify save was NEVER called (because email was invalid) verify(mockRepository, never()).save(any()); } @Test void testGetUserById_WhenUserNotFound() { // Tell mock to return null (user not found) when(mockRepository.findById(999L)).thenReturn(null); User result = service.getUserById(999L); assertNull(result); verify(mockRepository).findById(999L); } @Test void testIsEmailValid() { // This method doesn't use repository at all! User validUser = new User(1L, "Test", "test@example.com"); User invalidUser = new User(2L, "Test", "invalid"); assertTrue(service.isEmailValid(validUser)); assertFalse(service.isEmailValid(invalidUser)); // Verify repository was NEVER called (no mocking needed!) verifyNoInteractions(mockRepository); }}verify(mock).method()Checks if the method was called EXACTLY ONCE. Like checking if someone used the toy exactly one time.
verify(mock, never()).method()Checks that the method was NEVER called. Like making sure nobody touched your toy!
verify(mock, times(3)).method()Checks if the method was called a specific number of times. Like counting how many times your friend played with the toy.
verifyNoInteractions(mock)Checks that the mock was NEVER used at all. Like making sure the toy is still in its box!
Step 4: Argument Matchers - Being Flexible
Sometimes you don't care about the EXACT value passed to a method. Argument matchers help with this:
import static org.mockito.ArgumentMatchers.*;@Testvoid testArgumentMatchers() { UserService service = new UserService(mockRepository); // Match ANY Long value when(mockRepository.findById(anyLong())) .thenReturn(new User(1L, "Any", "any@example.com")); // Now it doesn't matter what ID we pass! User user1 = service.getUserById(1L); User user2 = service.getUserById(999L); User user3 = service.getUserById(42L); // All return the same mock user! assertEquals("Any", user1.getName()); assertEquals("Any", user2.getName()); assertEquals("Any", user3.getName());}@Testvoid testSaveAnyUser() { UserService service = new UserService(mockRepository); // We don't care WHICH user is saved, just that save is called User user = new User(1L, "Test", "test@example.com"); service.createUser(user); // Verify save was called with ANY User object verify(mockRepository).save(any(User.class));}@Testvoid testComplexMatching() { // Only match if email contains "@example.com" when(mockRepository.findById(anyLong())) .thenAnswer(invocation -> { Long id = invocation.getArgument(0); return new User(id, "User" + id, "user" + id + "@example.com"); }); User user = service.getUserById(5L); assertEquals("user5@example.com", user.getEmail());}🎯 Common Argument Matchers:
any()Matches any object
anyString()Any string value
anyInt()Any integer
anyLong()Any long number
anyList()Any list
eq(value)Exact value match
Real-World Example: Email Service
Let's test a service that sends emails without actually sending emails!
// EmailSender.java - Interface for sending emailspublic interface EmailSender { void sendEmail(String to, String subject, String body);}// NotificationService.java - Sends notificationspublic class NotificationService { private EmailSender emailSender; private UserRepository userRepository; public NotificationService(EmailSender emailSender, UserRepository userRepository) { this.emailSender = emailSender; this.userRepository = userRepository; } public void notifyUser(Long userId, String message) { User user = userRepository.findById(userId); if (user == null) { throw new IllegalArgumentException("User not found!"); } String subject = "Notification for " + user.getName(); emailSender.sendEmail(user.getEmail(), subject, message); } public void notifyAllUsers(String message) { List<User> users = userRepository.findAll(); for (User user : users) { emailSender.sendEmail(user.getEmail(), "Broadcast", message); } }}// NotificationServiceTest.java@ExtendWith(MockitoExtension.class)class NotificationServiceTest { @Mock private EmailSender mockEmailSender; @Mock private UserRepository mockRepository; private NotificationService service; @BeforeEach void setUp() { service = new NotificationService(mockEmailSender, mockRepository); } @Test void testNotifyUser_Success() { // Setup: User exists User user = new User(1L, "Alice", "alice@example.com"); when(mockRepository.findById(1L)).thenReturn(user); // Test service.notifyUser(1L, "Hello Alice!"); // Verify email was sent to correct address verify(mockEmailSender).sendEmail( eq("alice@example.com"), contains("Alice"), eq("Hello Alice!") ); } @Test void testNotifyUser_UserNotFound() { // Setup: User doesn't exist when(mockRepository.findById(999L)).thenReturn(null); // Test: Should throw exception assertThrows(IllegalArgumentException.class, () -> { service.notifyUser(999L, "Hello!"); }); // Verify email was NEVER sent verify(mockEmailSender, never()).sendEmail(anyString(), anyString(), anyString()); } @Test void testNotifyAllUsers() { // Setup: Three users in database List<User> users = Arrays.asList( new User(1L, "Alice", "alice@example.com"), new User(2L, "Bob", "bob@example.com"), new User(3L, "Carol", "carol@example.com") ); when(mockRepository.findAll()).thenReturn(users); // Test service.notifyAllUsers("System maintenance tonight!"); // Verify email sent to ALL users verify(mockEmailSender, times(3)).sendEmail( anyString(), eq("Broadcast"), eq("System maintenance tonight!") ); // Verify specific emails verify(mockEmailSender).sendEmail("alice@example.com", "Broadcast", "System maintenance tonight!"); verify(mockEmailSender).sendEmail("bob@example.com", "Broadcast", "System maintenance tonight!"); verify(mockEmailSender).sendEmail("carol@example.com", "Broadcast", "System maintenance tonight!"); }}🎉 What Did We Achieve?
- ✅ Tested email functionality WITHOUT sending real emails
- ✅ Tested database interactions WITHOUT a real database
- ✅ Tests run in milliseconds (not minutes!)
- ✅ No external dependencies - tests work anywhere!
Best Practices & Tips
✅ DO: Mock external dependencies
Databases, APIs, file systems, email servers - these should be mocked in unit tests.
❌ DON'T: Mock everything
Don't mock simple classes like String or Integer. Don't mock the class you're testing!
💡 TIP: Keep mocks simple
If your mock setup is super complex, maybe your real code is too complicated. Consider refactoring!
🎯 REMEMBER: Unit tests vs Integration tests
Mocks are for UNIT tests (testing one class). Integration tests use real components working together!
Key Takeaways
Mocks Are Toys!
Just like toy phones or toy kitchens, mocks pretend to be real but are perfect for practice (testing)!
Fast & Free
Mocks make your tests lightning-fast and don't cost money. Run thousands of tests in seconds!
Focus on YOUR Code
By mocking dependencies, you test only YOUR logic, not someone else's database or API.
Mockito Makes It Easy
With @Mock, when(), and verify(), creating test doubles is simple and readable!