Home/Testing/Mocking

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):

xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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):

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 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 class
public class User {
private Long id;
private String name;
private String email;
// Constructor, getters, setters...
}
// UserService.java - The class we want to test
public 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:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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 Mockito
class 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:

@Mock

Creates 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

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@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:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import static org.mockito.ArgumentMatchers.*;
@Test
void 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());
}
@Test
void 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));
}
@Test
void 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!

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// EmailSender.java - Interface for sending emails
public interface EmailSender {
void sendEmail(String to, String subject, String body);
}
// NotificationService.java - Sends notifications
public 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!