Java Testing Frameworks — Mockito

Java Testing Frameworks — Mockito 🧪

A Practical Guide to Isolating Dependencies in Java

👾 What is Mockito?

In the previous article, we explored how to perform unit testing with JUnit 5. Building on that foundation, let’s look at Mockito, an open-source testing framework for Java. Mockito makes it simple to create test doubles, mock objects that help us isolate and verify the behavior of the code under test.

🀄 What is a Test Double?

A test double is an object that takes the place of a real object in a unit test. Think of it like a stunt double in a movie, someone who steps in to perform certain actions instead of the main actor.

When writing unit tests, you’ll often encounter objects such as mocks, fakes, stubs, and spies. Collectively, these are known as test doubles. Their purpose is to temporarily replace real objects so that we can run tests in isolation.

But why would we ever want to substitute a real object with a fake one? Let’s look at an example method to understand this better.

public User createUser(User user, DAO mySqlDAO) {
// Code
String userID = mySQLDAO.save(user);
// More Code
}

In this example, createUser is the method under test, while mySqlDAO is its dependency. Our focus is on verifying the behavior of createUser, not on testing the save method inside mySqlDAO.

If we rely on the real mySqlDAO during testing, the test would attempt to persist data into the database, something we want to avoid in a unit test. To prevent this, we can use Mockito to create a mock version of mySqlDAO. This mock object simulates the behavior of the real dependency without performing any actual database operations, allowing us to isolate and properly test the logic within createUser.

⚙️ Adding Mocking to a Project

Go to mvnrepository.com and search for Mockito JUnit Jupiter and add the latest version to your project. In this article, we will be using Maven to setup the project.

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>4.5.1</version>
<scope>test</test>
</dependency>

Also, include the JUnit Aggregator dependency as well.

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>

In addition, add the maven-surefire-plugin as well.

<build>   
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M6</version>
</plugin>
</plugins>
</build>

💿 Initial Code Setup

Now, assume the following interface and its implementation.

public interface UserService {
void User createUser(String firstName, String lastName, String email, String password, String repeatPassword);
}
import com.nipunaupeksha.User;
import java.util.UUID;

public class UserServiceImpl implements UserService {
@Override
public User createUser(String firstName, String lastName, String email, String password, String repeatPassword) {
if (firstName == null || firstName.trim().length() == 0) {
throw new IllegalArgumentException("User's first name is empty.");
}
if (lastName == null || lastName.trim().length() == 0) {
throw new IllegalArgumentException("User's last name is empty.");
}
return new User(firstName, lastName, email, UUID.randomUUID().toString());
}
}

At this point, there’s no need to use a mock object, since the method has no dependencies to replace.

Now, let’s extend the scenario. Suppose we introduce a UserRepository interface along with its implementation, UserRepositoryImpl, and use an instance of it to persist data to the database.

import com.nipunaupeksha.User;

public interaface UserRepository {
boolean save(User user);
}
import com.nipunaupeksha.User;
import java.util.Map;

public class UserRepositoryImpl implements UserRepository {
Map<String, User> users = new HashMap<>();
@Override
public boolean save(User user) {
boolean returnValue = false;
if (!users.containsKey(user.getId()) {
users.put(user.getId(), user);
returnValue = true;
}
return returnValue;
}
}

Now, we can update our createUser method as follows.

import com.nipunaupeksha.User;
import java.util.UUID;

public class UserServiceImpl implements UserService {
UserRepository userRepository;

public UserServiceImpl(UserRepository userRepositoy) {
this.userRepository = userRepository;
}

@Override
public User createUser(String firstName, String lastName, String email, String password, String repeatPassword) {
if (firstName == null || firstName.trim().length() == 0) {
throw new IllegalArgumentException("User's first name is empty.");
}
if (lastName == null || lastName.trim().length() == 0) {
throw new IllegalArgumentException("User's last name is empty.");
}
User user = new User(firstName, lastName, email, UUID.randomUUID().toString());
boolean isUserCreated = userRepository.save(user);
if (!isUserCreated) throw new UserServiceException("Could not create user");
return user;
}
}

Also, let’s create a UserServiceException class as well.

public class UserServiceException extends RuntimeException {
public UserServiceException(String message) {
super(message);
}
}

Now, if we try to execute a unit test without creating a mock object, the userRepository.save(user) will be executed and the test user will persist on the database. Therefore, when unit testing createUser method, we need to isolate that method from other dependencies (in our case userRepository). Because we are not actually testing the code inside userRepository.save() method. Therefore, we need to create a mock for UserRepositoryImpl class.

💿 Mock Objects and Mock Object Injection

Now, let’s write a test using Mockito. To enable Mockito with JUnit 5, we need to annotate the test class with @ExtendWith(MockitoExtension.class).

Since UserServiceImpl depends on UserRepository, we need a mock of that dependency. We can create it by declaring a UserRepository userRepository field and annotating it with @Mock, which tells Mockito to generate a mock instance for us.

Next, we want to inject this mock into UserServiceImpl. To achieve this, we define a UserServiceImpl field and annotate it with @InjectMocks. Unlike when working with the UserService interface, here we use the concrete UserServiceImpl class so that Mockito can properly inject the mocked dependencies.

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
@InjectMocks
UserServiceImpl userService;

@Mock
UserRepository userRepository;

String firstName;
String lastName;
String email;
String password;
String repeatPassword;

@BeforeEach
void init(){
firstName = "Nipuna";
lastName = "Upeksha";
email = "[email protected]";
password = "12345678";
repeatPassword = "12345678";
}
...
}

💿 Stub a Method using Mockito’s Argument Matcher

Method stubbing is the process of defining what a mock object should return (or do) when one of its methods is called during a test.

In other words, instead of relying on the actual implementation of a method, you stub it with predefined behavior. This allows you to isolate the unit under test and control its dependencies.

With Mockito, we can achieve this using argument matchers like any().

Mockito.when(userRepository.save(Mockito.any(User.class))).thenReturn(true);

Here, no matter which User object is passed to save(), Mockito will stub the method to return true.

import org.junit.jupiter.api.Assertion;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjtectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.assertions.assertEquals;
import static org.junit.jupiter.api.assertions.assertNotNull;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
@InjectMocks
UserServiceImpl userService;

@Mock
UserRepository userRepository;

String firstName;
String lastName;
String email;
String password;
String repeatPassword;

@BeforeEach
void init() {
firstName = "Nipuna";
lastName = "Upeksha";
email = "[email protected]";
password = "12345678";
repeatPassword = "12345678";
}

@DisplayName("User object created")
@Test
void testCreateUser_whenUserDetailsProvided_returnsUserObject() {
// Arrange
when(userRepository.save(any(User.class))).thenReturn(true);
// Act
User user = userService.createUser(firstName, lastName, email, password, repeatPassword);
// Assert
assertNotNull(user, "The createUser() should not have returned null");
assertEquals(firstName, user.getFirstName(), "User's first name is incorrect");
assertEquals(lastName, user.getLastName(), "User's last name is incorrect");
assertEquals(email, user.getEmail(), "User's email is incorrect");
assertNotNull(user.getId(), "User id is missing");
}
}

If you place a debug point inside the save() method and run this test, you’ll notice that the breakpoint is never triggered. This shows that the test executed the createUser() method without actually calling save().

💿 Mockito Verify

One powerful feature of mock objects is that they let us verify how many times a particular method was invoked. In this case, we want to ensure that save() is called exactly once. If it’s not called at all or if it’s called more than once, we should be able to detect that. Mockito provides the verify() method for precisely this purpose.

import org.junit.jupiter.api.Assertion;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjtectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.assertions.assertEquals;
import static org.junit.jupiter.api.assertions.assertNotNull;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
@InjectMocks
UserServiceImpl userService;

@Mock
UserRepository userRepository;

String firstName;
String lastName;
String email;
String password;
String repeatPassword;

@BeforeEach
void init() {
firstName = "Nipuna";
lastName = "Upeksha";
email = "[email protected]";
password = "12345678";
repeatPassword = "12345678";
}

@DisplayName("User object created")
@Test
void testCreateUser_whenUserDetailsProvided_returnsUserObject() {
// Arrange
when(userRepository.save(any(User.class))).thenReturn(true);
// Act
User user = userService.createUser(firstName, lastName, email, password, repeatPassword);
// Assert
assertNotNull(user, "The createUser() should not have returned null");
assertEquals(firstName, user.getFirstName(), "User's first name is incorrect");
assertEquals(lastName, user.getLastName(), "User's last name is incorrect");
assertEquals(email, user.getEmail(), "User's email is incorrect");
assertNotNull(user.getId(), "User id is missing");
verify(userRepository, times(1)).save(any(User.class));
}
}

💿 Exception Stubbing

If we want to test the exceptions thrown by the save() method, we need to use exception stubbing. To set this up, we first need to adjust the createUser() method accordingly.

import com.nipunaupeksha.User;
import java.util.UUID;

public class UserServiceImpl implements UserService{
UserRepository userRepository;
public UserServiceImpl(UserRepository userRepositoy){
this.userRepository = userRepository;
}
@Override
public User createUser(String firstName, String lastName, String email, String password, String repeatPassword) {
if (firstName == null || firstName.trim().length() == 0) {
throw new IllegalArgumentException("User's first name is empty.");
}
if (lastName == null || lastName.trim().length() == 0) {
throw new IllegalArgumentException("User's last name is empty.");
}
User user = new User(firstName, lastName, email, UUID.randomUUID().toString());
boolean isUserCreated;
try {
isUserCreated = userRepository.save(user);
} catch (RuntimeException ex) {
throw new UserServiceException(ex.getMessage());
}
if(!isUserCreated) throw new UserServiceException("Could not create user");
return user;
}
}
import org.junit.jupiter.api.Assertion;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjtectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.assertions.assertEquals;
import static org.junit.jupiter.api.assertions.assertNotNull;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
@InjectMocks
UserServiceImpl userService;

@Mock
UserRepository userRepository;

String firstName;
String lastName;
String email;
String password;
String repeatPassword;

@BeforeEach
void init() {
firstName = "Nipuna";
lastName = "Upeksha";
email = "[email protected]";
password = "12345678";
repeatPassword = "12345678";
}

@DisplayName("If save() method causes a RuntimeException, a UserServiceException is thrown")
@Test
void testCreateUser_whenSaveMethodThrowsException_thenThrowsUserServiceException() {
// Arrange
when(userRepository.save(any(User.class))).thenThrow(RuntimeException.class);
// Act
assertThrows(UserServiceException.class, () -> {
userService.createUser(firstName, lastName, email, password, repeatPassword);
}, "Should have thrown UserServiceException instead.");
}
}

💿 Stubbing void Methods

The when(userRepository.save(any(User.class))).thenThrow(RuntimeException.class); can be used when there is a return type for the method under test. However, if they have void return type the above type of mocking won’t work.

So, first let’s create an interface and an implementation class which will have a void method.

public interface EmailVerificationService {
void scheduleEmailContirmation(User user);
}
public class EmailVerificationServiceImpl implements EmailVerificationService {
void scheduleEmailConfirmation(User user) {
// Put user details into email queue
}
}

Let’s also create a new exception class as well.

public class EmailNotificationServiceException extends RuntimeException {
public EmailNotificationServiceException(String message) {
super(message);
}
}

Then we can refactor the service to have the EmailVerificationService.

import java.util.UUID;

public class UserServiceImpl implements UserService {
UserRepository userRepository;
EmailVerificationService emailVerificationService;

public UserServiceImpl(UserRepository userRepositoy, EmailVerificationService emailVerificationService){
this.userRepository = userRepository;
this.emailVerificationSevice = emailVerificationService;
}

@Override
public User createUser(String firstName, String lastName, String email, String password, String repeatPassword) {
if (firstName == null || firstName.trim().length() == 0) {
throw new IllegalArgumentException("User's first name is empty.");
}
if (lastName == null || lastName.trim().length() == 0) {
throw new IllegalArgumentException("User's last name is empty.");
}
User user = new User(firstName, lastName, email, UUID.randomUUID().toString());
boolean isUserCreated;
try {
isUserCreated = userRepository.save(user);
} catch (RuntimeException ex) {
throw new UserServiceException(ex.getMessage());
}
if (!isUserCreated) throw new UserServiceException("Could not create user");
try {
emailVerificationService.scheduleEmailConfirmation(user);
} catch (RuntimeException ex) {
throw new UserServiceException(ex.getMessage());
}
return user;
}
}

Now let’s create a test for this. But first, make sure to add EmailVerificationServiceImpl as a @Mock object as well.

import org.junit.jupiter.api.Assertion;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjtectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.assertions.assertEquals;
import static org.junit.jupiter.api.assertions.assertNotNull;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
@InjectMocks
UserServiceImpl userService;

@Mock
UserRepository userRepository;

@Mock
EmailVerificationServiceImpl emailVerificationService;
String firstName;
String lastName;
String email;
String password;
String repeatPassword;

@BeforeEach
void init() {
firstName = "Nipuna";
lastName = "Upeksha";
email = "[email protected]";
password = "12345678";
repeatPassword = "12345678";
}

@DisplayName("If save() method causes a RuntimeException, a UserServiceException is thrown")
@Test
void testCreateUser_whenSaveMethodThrowsException_thenThrowsUserServiceException() {
// Arrange
when(userRepository.save(any(User.class))).thenThrow(RuntimeException.class);
// Act
assertThrows(UserServiceException.class, () -> {
userService.createUser(firstName, lastName, email, password, repeatPassword);
}, "Should have thrown UserServiceException instead.");
}

@DisplayName("EmailNotificationConfirmation Exception is handled")
@Test
void testCreateUser_whenEmailNotificatoinExceptionThrown_thenThrowsUserServiceException() {
// Arrange
when(userRepository.save(any(User.class))).thenReturn(true);
doThrow(EmailNotificationServiceException.class).when(emailVerificationService)
.scheduleEmailConfirmation(any(User.class));
// Act & Assert
assertThrows(UserServiceException.class, () -> {
userService.createUser(firstName, lastName, email, password, repeatPassword);
}, "Should have thrown UserServiceException instead");
// Assert
verify(emailVerificationService, times(1))
.scheduleEmailConfirmation(any(User.class));
}
}

Rather than doThrow() we can also use doNothing() method as well.

doNothing().when(emailVerificatoinService).scheduleEmailConfirmation(any(User.class));

💿 Mocking Real Objects

Although its beneficial to use mock objects, sometimes you have to use real objects when testing as well.

import org.junit.jupiter.api.Assertion;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjtectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.assertions.assertEquals;
import static org.junit.jupiter.api.assertions.assertNotNull;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
@InjectMocks
UserServiceImpl userService;

@Mock
UserRepository userRepository;

@Mock
EmailVerificationServiceImpl emailVerificationService;
String firstName;
String lastName;
String email;
String password;
String repeatPassword;

@BeforeEach
void init() {
firstName = "Nipuna";
lastName = "Upeksha";
email = "[email protected]";
password = "12345678";
repeatPassword = "12345678";
}

@DisplayName("If save() method causes a RuntimeException, a UserServiceException is thrown")
@Test
void testCreateUser_whenSaveMethodThrowsException_thenThrowsUserServiceException() {
// Arrange
when(userRepository.save(any(User.class))).thenThrow(RuntimeException.class);
// Act
assertThrows(UserServiceException.class, () -> {
userService.createUser(firstName, lastName, email, password, repeatPassword);
}, "Should have thrown UserServiceException instead.");
}

@DisplayName("EmailNotificationConfirmation Exception is handled")
@Test
void testCreateUser_whenEmailNotificatoinExceptionThrown_thenThrowsUserServiceException() {
// Arrange
when(userRepository.save(any(User.class))).thenReturn(true);
doThrow(EmailNotificationServiceException.class).when(emailVerificationService)
.scheduleEmailConfirmation(any(User.class));
// Act & Assert
assertThrows(UserServiceException.class, () -> {
userService.createUser(firstName, lastName, email, password, repeatPassword);
}, "Should have thrown UserServiceException instead");
// Assert
verify(emailVerificationService, times(1))
.scheduleEmailConfirmation(any(User.class));
}

@DisplayName("Schedule Email Confirmation is executed")
@Test
void testCreateUser_whenUserCreated_scheduleEmailConfirmation(){
// Arrange
when(userRepository.save(any(User.class))).thenReturn(true);
doCallRealMethod().when(emailVerificationService)
.scheduleEmailConfirmaiton(any(User.class));
// Act
userService.createUser(firstName, lastName, email, password, repeatPassword);
// Assert
verify(emailVerificationService, times(1))
.scheduleEmailConfirmation(any(User.class));
}
}

🏁 Conclusion

So this is all regarding how to use Mockito to do unit testing. Let’s talk about how you can use other code coverage and Spring Testing Framework in another article. If you have thoughts or suggestions for me feel free to let me know.


Java Testing Frameworks — Mockito 🧪 was originally published in Javarevisited on Medium, where people are continuing the conversation by highlighting and responding to this story.

This post first appeared on Read More