Introduction
In this chapter, we will learn about mocking constructors using Mockito. Constructor mocking allows you to control the behavior of objects that are created within the class under test. This can be particularly useful when the class being instantiated performs complex initialization or interacts with external systems.
What is Mocking Constructors?
Mocking constructors involves intercepting the creation of an object using its constructor and replacing it with a mock. This is useful for isolating tests from the actual implementations of the classes being instantiated.
Setting Up Mockito to Mock Constructors
To mock constructors with Mockito, you need to use the mockito-inline
library, which provides the necessary functionality. Ensure that you have the following dependencies in your pom.xml
if you are using Maven:
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-inline -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
<!-- JUnit 5 dependency -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
Example: EmailService Class and NotificationService Class
In this example, we have a NotificationService
class that uses an EmailService
object. We will mock the constructor of EmailService
to test the NotificationService
class.
Class Under Test: NotificationService
public class NotificationService {
public boolean sendNotification(String email, String message) {
EmailService emailService = new EmailService(email);
return emailService.sendEmail(message);
}
}
Class to be Mocked: EmailService
public class EmailService {
private String email;
public EmailService(String email) {
this.email = email;
}
public boolean sendEmail(String message) {
// Logic to send email
return true;
}
}
Test Class: NotificationServiceTest
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
import org.mockito.MockedConstruction;
import static org.junit.jupiter.api.Assertions.*;
public class NotificationServiceTest {
@Test
void testSendNotification_MockConstructor() {
try (MockedConstruction<EmailService> mocked = mockConstruction(EmailService.class,
(mock, context) -> {
// Mock the sendEmail method
when(mock.sendEmail(anyString())).thenReturn(false);
})) {
// Create an instance of NotificationService
NotificationService notificationService = new NotificationService();
// Call the method under test
boolean result = notificationService.sendNotification("test@example.com", "Hello!");
// Assert the result
assertFalse(result);
// Verify that the constructor was called
assertEquals(1, mocked.constructed().size());
verify(mocked.constructed().get(0)).sendEmail("Hello!");
}
}
}
Explanation
- Mocking the Constructor:
try (MockedConstruction<EmailService> mocked = mockConstruction(EmailService.class, (mock, context) -> { // Mock the sendEmail method when(mock.sendEmail(anyString())).thenReturn(false); })) {
This line creates a mock construction for the
EmailService
class. ThemockConstruction
method allows us to intercept the creation ofEmailService
objects and replace them with mocks. - Creating an Instance of NotificationService:
NotificationService notificationService = new NotificationService();
This line creates an instance of the
NotificationService
class. - Calling the Method Under Test:
boolean result = notificationService.sendNotification("test@example.com", "Hello!");
This line calls the
sendNotification
method on theNotificationService
instance with the specified arguments. - Asserting the Result:
assertFalse(result);
This line asserts that the returned value matches the expected result (
false
). - Verifying the Constructor and Method Calls:
assertEquals(1, mocked.constructed().size()); verify(mocked.constructed().get(0)).sendEmail("Hello!");
These lines verify that the constructor was called once and that the
sendEmail
method was called with the specified argument.
Output
Example: DatabaseService Class and UserService Class
Let’s consider another practical example where we have a UserService
class that uses a DatabaseService
object. We will mock the constructor of DatabaseService
to test the UserService
class.
Class Under Test: UserService
public class UserService {
public boolean createUser(String username, String password) {
DatabaseService databaseService = new DatabaseService();
return databaseService.saveUser(username, password);
}
}
Class to be Mocked: DatabaseService
public class DatabaseService {
public boolean saveUser(String username, String password) {
// Logic to save user to the database
return true;
}
}
Test Class: UserServiceTest
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
import org.mockito.MockedConstruction;
import static org.junit.jupiter.api.Assertions.*;
public class UserServiceTest {
@Test
void testCreateUser_MockConstructor() {
try (MockedConstruction<DatabaseService> mocked = mockConstruction(DatabaseService.class,
(mock, context) -> {
// Mock the saveUser method
when(mock.saveUser(anyString(), anyString())).thenReturn(false);
})) {
// Create an instance of UserService
UserService userService = new UserService();
// Call the method under test
boolean result = userService.createUser("testuser", "password");
// Assert the result
assertFalse(result);
// Verify that the constructor was called
assertEquals(1, mocked.constructed().size());
verify(mocked.constructed().get(0)).saveUser("testuser", "password");
}
}
}
Explanation
- Mocking the Constructor:
try (MockedConstruction<DatabaseService> mocked = mockConstruction(DatabaseService.class, (mock, context) -> { // Mock the saveUser method when(mock.saveUser(anyString(), anyString())).thenReturn(false); })) {
This line creates a mock construction for the
DatabaseService
class. ThemockConstruction
method allows us to intercept the creation ofDatabaseService
objects and replace them with mocks. - Creating an Instance of UserService:
UserService userService = new UserService();
This line creates an instance of the
UserService
class. - Calling the Method Under Test:
boolean result = userService.createUser("testuser", "password");
This line calls the
createUser
method on theUserService
instance with the specified arguments. - Asserting the Result:
assertFalse(result);
This line asserts that the returned value matches the expected result (
false
). - Verifying the Constructor and Method Calls:
assertEquals(1, mocked.constructed().size()); verify(mocked.constructed().get(0)).saveUser("testuser", "password");
These lines verify that the constructor was called once and that the
saveUser
method was called with the specified arguments.
Conclusion
Mocking constructors using Mockito allows you to isolate your tests from the actual implementations of the classes being instantiated. By using the mockito-inline
library, you can mock constructors and verify their interactions, making your tests more robust and maintainable. Understanding and utilizing constructor mocking will help you write more effective and isolated unit tests. In this chapter, we covered two examples: mocking the constructor of an EmailService
class in a NotificationService
test and mocking the constructor of a DatabaseService
class in a UserService
test. These examples demonstrate how to use Mockito to mock constructors and verify their behavior in different scenarios.