Partial Mocking with Mockito: Balancing Real and Mock Implementations

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() and getQuantity() 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 of Stock.
  • Specific methods (getPrice() and getQuantity()) 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.

Leave a Reply

Your email address will not be published. Required fields are marked *