Introduction
In this chapter, we will learn about mocking final classes and methods using Mockito. Final classes and methods cannot be subclassed, which can make them difficult to mock in tests. However, Mockito provides functionality to mock these as well, allowing you to isolate your tests from the actual implementations and making them more robust and easier to maintain.
What is Mocking Final Classes and Methods?
Mocking final classes and methods involves creating a mock for classes and methods that are declared as final
. This is useful for isolating tests from the actual implementations of final methods and classes, which cannot be overridden.
Setting Up Mockito to Mock Final Classes and Methods
To mock final classes and methods 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 1: Simple Final Class with Final Method
Let’s start with a simple final class that has a final method. We will mock this final method in a test.
Final Class with Final Method
public final class SimpleFinalClass {
public final String greet(String name) {
return "Hello, " + name;
}
}
Test Class: SimpleFinalClassTest
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class SimpleFinalClassTest {
@Test
void testGreet_MockFinalMethod() {
// Create a mock object of SimpleFinalClass
SimpleFinalClass simpleFinalClass = mock(SimpleFinalClass.class);
// Mock the final method
when(simpleFinalClass.greet(anyString())).thenReturn("Hi there!");
// Call the method under test
String result = simpleFinalClass.greet("John");
// Assert the result
assertEquals("Hi there!", result);
// Verify that the final method was called
verify(simpleFinalClass).greet("John");
}
}
Explanation
- Creating Mocks:
SimpleFinalClass simpleFinalClass = mock(SimpleFinalClass.class);
This line creates a mock object of the
SimpleFinalClass
final class. - Mocking Final Method:
when(simpleFinalClass.greet(anyString())).thenReturn("Hi there!");
This line mocks the
greet
final method to return"Hi there!"
regardless of the input argument. - Calling the Method Under Test:
String result = simpleFinalClass.greet("John");
This line calls the
greet
method on theSimpleFinalClass
instance with the argument"John"
. - Asserting the Result:
assertEquals("Hi there!", result);
This line asserts that the returned value matches the expected result
"Hi there!"
. - Verifying the Final Method Was Called:
verify(simpleFinalClass).greet("John");
This line verifies that the
greet
final method was called with the argument"John"
.
Example 2: Utility Final Class with Final Methods
Now let’s consider a more practical example where we have a final class MathUtils
with final methods add
and multiply
. We will mock these methods to test a CalculatorService
class that uses these methods.
Final Class with Final Methods
public final class MathUtils {
public final int add(int a, int b) {
return a + b;
}
public final int multiply(int a, int b) {
return a * b;
}
}
Service Class Using Final Methods
public class CalculatorService {
private final MathUtils mathUtils;
public CalculatorService(MathUtils mathUtils) {
this.mathUtils = mathUtils;
}
public int calculateSum(int a, int b) {
return mathUtils.add(a, b);
}
public int calculateProduct(int a, int b) {
return mathUtils.multiply(a, b);
}
}
Test Class: CalculatorServiceTest
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class CalculatorServiceTest {
@Test
void testCalculateSum_MockFinalMethod() {
// Create a mock object of MathUtils
MathUtils mathUtils = mock(MathUtils.class);
// Mock the final method
when(mathUtils.add(anyInt(), anyInt())).thenReturn(10);
// Create an instance of CalculatorService
CalculatorService calculatorService = new CalculatorService(mathUtils);
// Call the method under test
int result = calculatorService.calculateSum(3, 7);
// Assert the result
assertEquals(10, result);
// Verify that the final method was called
verify(mathUtils).add(3, 7);
}
@Test
void testCalculateProduct_MockFinalMethod() {
// Create a mock object of MathUtils
MathUtils mathUtils = mock(MathUtils.class);
// Mock the final method
when(mathUtils.multiply(anyInt(), anyInt())).thenReturn(20);
// Create an instance of CalculatorService
CalculatorService calculatorService = new CalculatorService(mathUtils);
// Call the method under test
int result = calculatorService.calculateProduct(4, 5);
// Assert the result
assertEquals(20, result);
// Verify that the final method was called
verify(mathUtils).multiply(4, 5);
}
}
Explanation
- Creating Mocks:
MathUtils mathUtils = mock(MathUtils.class);
This line creates a mock object of the
MathUtils
final class. - Mocking Final Methods:
when(mathUtils.add(anyInt(), anyInt())).thenReturn(10); when(mathUtils.multiply(anyInt(), anyInt())).thenReturn(20);
These lines mock the
add
andmultiply
final methods to return10
and20
respectively, regardless of the input arguments. - Creating an Instance of CalculatorService:
CalculatorService calculatorService = new CalculatorService(mathUtils);
This line creates an instance of the
CalculatorService
class with the mock dependency. - Calling the Methods Under Test:
int resultSum = calculatorService.calculateSum(3, 7); int resultProduct = calculatorService.calculateProduct(4, 5);
These lines call the
calculateSum
andcalculateProduct
methods on theCalculatorService
instance with the specified arguments. - Asserting the Results:
assertEquals(10, resultSum); assertEquals(20, resultProduct);
These lines assert that the returned values match the expected results.
- Verifying the Final Methods Were Called:
verify(mathUtils).add(3, 7); verify(mathUtils).multiply(4, 5);
These lines verify that the
add
andmultiply
final methods were called with the specified arguments.
Output
Conclusion
Mocking final classes and methods using Mockito allows you to isolate your tests from the actual implementations of final methods and classes. By using the mockito-inline
library, you can mock final methods and verify their interactions, making your tests more robust and maintainable. Understanding and utilizing final method mocking will help you write more effective and isolated unit tests. In this chapter, we covered two examples: a simple final class and a utility final class with methods used in a service class. These examples demonstrate how to use Mockito to mock final methods and verify their behavior in different scenarios.