Java > Testing in Java > Unit Testing > Mocking with Mockito
Unit Testing with Mockito: Basic Example
This snippet demonstrates how to use Mockito to create a mock object and verify its interactions during unit testing in Java. We will explore creating a simple service and a repository, mocking the repository, and verifying that the service calls the repository as expected. This isolates the service's logic from the real data layer during testing.
Defining the Repository Interface
This interface represents a data access layer. It defines the methods for retrieving and saving user data. In a real application, this would interact with a database.
public interface UserRepository {
User findById(Long id);
void save(User user);
}
Defining the User Service
This class represents a service that utilizes the UserRepository. The constructor takes a UserRepository instance, allowing for dependency injection and making it easier to mock during testing. The `getUser` method retrieves a user by ID, and the `createUser` method saves a new user after performing some validation.
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUser(Long id) {
return userRepository.findById(id);
}
public void createUser(User user) {
// Perform some business logic before saving (e.g., validation)
if (user.getName() == null || user.getName().isEmpty()) {
throw new IllegalArgumentException("User name cannot be empty.");
}
userRepository.save(user);
}
}
Creating the User Class
Simple `User` POJO class with id and name.
public class User {
private Long id;
private String name;
public User() {}
public User(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Unit Test with Mockito
This test class demonstrates how to use Mockito to test the UserService. * We first create a mock UserRepository using `Mockito.mock(UserRepository.class)`. This creates a mock object that implements the UserRepository interface. * Then, we create an instance of UserService, injecting the mocked UserRepository. * We use `when(userRepository.findById(1L)).thenReturn(expectedUser)` to define the behavior of the mock object. This tells Mockito that when the `findById` method is called with the argument `1L`, it should return the `expectedUser`. * We then call the `getUser` method on the UserService and assert that the returned user is equal to the `expectedUser`. * Finally, we use `verify(userRepository, times(1)).findById(1L)` to verify that the `findById` method was called exactly once with the argument `1L`. The `createUser` tests check that the `save` method is called correctly and that an exception is thrown when an invalid user is provided. `never()` is used to verify that the method isn't called when an exception is expected.
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
class UserServiceTest {
@Test
void getUser_validId_returnsUser() {
// Arrange
UserRepository userRepository = Mockito.mock(UserRepository.class);
UserService userService = new UserService(userRepository);
User expectedUser = new User(1L, "John Doe");
when(userRepository.findById(1L)).thenReturn(expectedUser);
// Act
User actualUser = userService.getUser(1L);
// Assert
assertEquals(expectedUser, actualUser);
verify(userRepository, times(1)).findById(1L);
}
@Test
void createUser_validUser_savesUser() {
// Arrange
UserRepository userRepository = Mockito.mock(UserRepository.class);
UserService userService = new UserService(userRepository);
User newUser = new User(null, "Jane Smith");
// Act
userService.createUser(newUser);
// Assert
verify(userRepository, times(1)).save(newUser);
}
@Test
void createUser_invalidUser_throwsException() {
// Arrange
UserRepository userRepository = Mockito.mock(UserRepository.class);
UserService userService = new UserService(userRepository);
User invalidUser = new User(null, null);
// Act & Assert
assertThrows(IllegalArgumentException.class, () -> userService.createUser(invalidUser));
verify(userRepository, never()).save(any());
}
}
Concepts Behind the Snippet
This snippet illustrates Dependency Injection (DI) and Inversion of Control (IoC). The `UserService` doesn't create its own `UserRepository`; instead, it receives it as a constructor argument. This makes the `UserService` easier to test because we can inject a mock `UserRepository` that behaves in a predictable way. Mockito is used to create and configure these mock objects.
Real-Life Use Case
Consider a complex banking application where `UserService` handles user authentication and authorization. `UserRepository` could represent interaction with a database or external authentication service. By mocking the `UserRepository`, you can isolate the authentication logic within the `UserService` and test it independently from the actual data access layer. This speeds up testing and makes it more reliable, as you don't have to rely on a functioning database or external service for every test.
Best Practices
Interview Tip
Be prepared to explain the benefits of unit testing and mocking. Understand the Arrange-Act-Assert pattern and be able to discuss different types of mocking frameworks, such as Mockito and EasyMock. Also, be able to explain the concept of test-driven development (TDD).
When to Use Mockito
Mockito is beneficial when you need to isolate a unit of code (e.g., a class) from its dependencies for testing. It's particularly useful when dealing with:
Alternatives
Pros
Cons
FAQ
-
What's the difference between `when()` and `doReturn()` in Mockito?
`when()` is the most common way to define stub behavior in Mockito. However, `doReturn()` is preferred when stubbing void methods or methods that throw exceptions. `when()` will actually execute the method being stubbed during the stubbing process, which can lead to unexpected behavior or errors, especially when the method has side effects. `doReturn()` avoids this by not executing the method during stubbing. -
What is `verify()` used for in Mockito?
`verify()` is used to ensure certain interactions with mock objects occurred during the test. You specify the mock object and the expected number of invocations for a specific method. This helps confirm that the unit under test interacts with its dependencies as intended. -
Why is dependency injection important for unit testing with Mockito?
Dependency injection makes it possible to replace real dependencies with mock objects. This allows you to isolate the class under test and control the behavior of its dependencies, which is essential for effective unit testing. Without DI, it's much harder (or impossible) to mock dependencies.