Introduction
In unit testing, especially when using frameworks like JUnit combined with a mocking library such as Mockito, it’s often necessary to isolate components under test. Sometimes this requires that certain methods of a class are mocked (i.e., their implementations are replaced by fake ones), while others are left untouched so they execute their real code. This technique is known as partial mocking and can be especially useful when testing classes with dependencies or complex logic that involve other classes.
Understanding Partial Mocking
Partial mocking allows you to create a mock object of a class but instruct specific methods within that class to use the actual implementation rather than a mocked version. This approach lets you test how your code interacts with a method’s real behavior, while still controlling and simulating external dependencies or complex conditions for other methods.
Use Cases
Partial mocking is useful when:
- You need to simulate certain behaviors (like service responses) but rely on actual implementations of others.
- A class under test invokes multiple methods that can’t all be realistically mocked.
- Testing legacy code, where refactoring might not be immediately feasible.
Implementing Partial Mocking in Mockito
Method 1: Using mock
with CALLS_REAL_METHODS
You can specify when creating a mock that unstubbed method calls should use the real implementations. This is achieved using Mockito.mock(Class<T> classToMock, Answer<?> answer)
where answer
is set to CALLS_REAL_METHODS
.
Example:
import org.junit.Test;
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;
public class StockTest {
public class Stock {
private final double price;
private final int quantity;
Stock(double price, int quantity) {
this.price = price;
this.quantity = quantity;
}
public double getPrice() {
return price;
}
public int getQuantity() {
return quantity;
}
public double getValue() {
return getPrice() * getQuantity();
}
}
@Test
public void getValueTest() {
Stock stock = mock(Stock.class, withSettings().defaultAnswer(CALLS_REAL_METHODS));
when(stock.getPrice()).thenReturn(100.00);
when(stock.getQuantity()).thenReturn(200);
double value = stock.getValue();
assertEquals("Stock value not correct", 100.00 * 200, value, .00001);
}
}
In this setup:
getPrice()
andgetQuantity()
are mocked to return predetermined values.- The real implementation of
getValue()
, which depends on these methods, is called.
Method 2: Using spy
A spy allows you to create a partial mock by starting with an actual instance of the class. You can then specify which methods should be mocked and leave others to perform their default behavior.
Example:
import org.junit.Test;
import static org.mockito.Mockito.*;
public class StockTest {
public class Stock {
private final double price;
private final int quantity;
Stock(double price, int quantity) {
this.price = price;
this.quantity = quantity;
}
public double getPrice() {
return price;
}
public int getQuantity() {
return quantity;
}
public double getValue() {
return getPrice() * getQuantity();
}
}
@Test
public void getValueTest() {
Stock stock = spy(new Stock(0, 0));
doReturn(100.00).when(stock).getPrice();
doReturn(200).when(stock).getQuantity();
double value = stock.getValue();
assertEquals("Stock value not correct", 100.00 * 200, value, .00001);
}
}
In this case:
spy
is used to start with a real instance ofStock
.- Specific methods (
getPrice()
andgetQuantity()
) are stubbed. - The original logic in
getValue()
executes using the actual method calls.
Method 3: Using doReturn
for Immediate Evaluation
With spies, there’s a potential pitfall where real methods are invoked before stubbing occurs. To avoid this, use doReturn(...).when(object).method()
, which ensures that the stub is set up first.
Example:
import org.junit.Test;
import static org.mockito.Mockito.*;
public class StockTest {
public class Stock {
private final double price;
private final int quantity;
Stock(double price, double quantity) {
this.price = price;
this.quantity = quantity;
}
public double getPrice() {
return price;
}
public int getQuantity() {
return quantity;
}
public double getValue() {
return getPrice() * getQuantity();
}
}
@Test
public void getValueTest() {
Stock stock = spy(new Stock(0, 0));
doReturn(100.00).when(stock).getPrice();
doReturn(200).when(stock).getQuantity();
double value = stock.getValue();
assertEquals("Stock value not correct", 100.00 * 200, value, .00001);
}
}
Key Considerations
- Design Implications: Partial mocks can indicate a need to refactor your code into more testable units.
- Performance: Avoid overusing partial mocks as they may lead to performance hits and increased complexity in tests.
Conclusion
Partial mocking is a powerful tool within Mockito, allowing for nuanced testing scenarios where some methods should behave realistically while others are controlled. By using mock
with CALLS_REAL_METHODS
, or employing spy
alongside careful stubbing techniques like doReturn
, you can effectively balance the use of real and mock implementations in your tests.