Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Introduction

In this short tutorial, we’ll explore the various options for effectively mocking constructors in Java using Mockito and PowerMock.

2. Mocking Constructors Using PowerMock

Mocking constructors or static methods is impossible using Mockito version 3.3 or lower. In such cases, a library like PowerMock provides additional capabilities that allow us to mock the behavior of constructors and orchestrate their interactions.

3. Model

Let’s simulate a payment processing system using two Java classes. We’ll create a PaymentService class that contains the logic for processing payments and offers the flexibility of both a parameterized constructor for specifying payment modes and a default constructor with a fallback mode:

public class PaymentService {
    private final String paymentMode;
    public PaymentService(String paymentMode) {
        this.paymentMode = paymentMode;
    }

    public PaymentService() {
        this.paymentMode = "Cash";
    }

    public String processPayment(){
        return this.paymentMode;
    }
}

The PaymentProcessor class depends on the PaymentService to carry out payment processing tasks and provides two constructors, one for the default setup and another for customizing the payment mode:

public class PaymentProcessor {
    private final PaymentService paymentService;
    public PaymentProcessor() {
        this.paymentService = new PaymentService();
    }

    public PaymentProcessor(String paymentMode) {
        this.paymentService = new PaymentService(paymentMode);
    }

    public String processPayment(){
        return paymentService.processPayment();
    }
}

4. Mocking Default Constructors Using Mockito

When writing unit tests, isolating the code we want to test is crucial. Constructors often create dependencies we don’t want to involve in our tests. Mocking constructors allow us to replace real objects with mock objects, ensuring that the behavior we’re testing is specific to the unit under examination.

Starting from Mockito version 3.4 and beyond, we gain access to the mockConstruction() method. It allows us to mock object constructions. We specify the class whose constructor we intend to mock as the first argument. Additionally, we provide a second argument in the form of a MockInitializer callback function. This callback function allows us to define and manipulate the behavior of the mock during construction:

@Test
void whenConstructorInvokedWithInitializer_ThenMockObjectShouldBeCreated(){
    try(MockedConstruction<PaymentService> mockPaymentService = Mockito.mockConstruction(PaymentService.class,(mock,context)-> {
        when(mock.processPayment()).thenReturn("Credit");
    })){
        PaymentProcessor paymentProcessor = new PaymentProcessor();
        Assertions.assertEquals(1,mockPaymentService.constructed().size());
        Assertions.assertEquals("Credit", paymentProcessor.processPayment());
    }
}

There are several overloaded versions of the mockConstruction() method, each catering to different use cases. In the scenario below, we don’t use the MockInitializer to initialize the mock object. We’re verifying that the constructor was called once, and the absence of an initializer ensures the paymentMode field’s null state in the constructed PaymentService object:

@Test
void whenConstructorInvokedWithoutInitializer_ThenMockObjectShouldBeCreatedWithNullFields(){
    try(MockedConstruction<PaymentService> mockPaymentService = Mockito.mockConstruction(PaymentService.class)){
        PaymentProcessor paymentProcessor = new PaymentProcessor();
        Assertions.assertEquals(1,mockPaymentService.constructed().size());
        Assertions.assertNull(paymentProcessor.processPayment());
    }
}

5. Mocking Parameterized Constructors Using Mockito

In this example, we’ve set up the MockInitializer and invoked the parameterized constructor. We’re verifying that there is precisely one mock created, and it has the desired value defined during initialization:

@Test
void whenConstructorInvokedWithParameters_ThenMockObjectShouldBeCreated(){
    try(MockedConstruction<PaymentService> mockPaymentService = Mockito.mockConstruction(PaymentService.class,(mock, context) -> {
        when(mock.processPayment()).thenReturn("Credit");
    })){
        PaymentProcessor paymentProcessor = new PaymentProcessor("Debit");
        Assertions.assertEquals(1,mockPaymentService.constructed().size());
        Assertions.assertEquals("Credit", paymentProcessor.processPayment());
    }
}

6. Scope of the Mocked Constructor

The try-with-resources construct in Java allows us to limit the scope of the mock being created. Within this block, any invocation of public constructors for the specified class creates mock objects. The real constructor will be invoked when it is called anywhere outside the block.

In the below example, we don’t define any initializer and invoke both the default and parameterized constructors multiple times. The behavior of the mock is then defined post-construction.

We’re verifying that three mock objects have indeed been created and are adhering to our predefined mock behavior:

@Test
void whenMultipleConstructorsInvoked_ThenMultipleMockObjectsShouldBeCreated(){
    try(MockedConstruction<PaymentService> mockPaymentService = Mockito.mockConstruction(PaymentService.class)){
        PaymentProcessor paymentProcessor = new PaymentProcessor();
        PaymentProcessor secondPaymentProcessor = new PaymentProcessor();
        PaymentProcessor thirdPaymentProcessor = new PaymentProcessor("Debit");

        when(mockPaymentService.constructed().get(0).processPayment()).thenReturn("Credit");
        when(mockPaymentService.constructed().get(1).processPayment()).thenReturn("Online Banking");

        Assertions.assertEquals(3,mockPaymentService.constructed().size());
        Assertions.assertEquals("Credit", paymentProcessor.processPayment());
        Assertions.assertEquals("Online Banking", secondPaymentProcessor.processPayment());
        Assertions.assertNull(thirdPaymentProcessor.processPayment());
    }
}

7. Dependency Injection and Constructor Mocking

When we use dependency injection, we can directly pass mock objects, avoiding the need to mock constructors. With this approach, we can mock the dependency before instantiating the class under test, eliminating the need to mock any constructors.

Let’s introduce a third constructor in the PaymentProcessor class where the PaymentService is injected as a dependency:

public PaymentProcessor(PaymentService paymentService) {
    this.paymentService = paymentService;
}

We have decoupled the dependency from the PaymentProcessor class, which allows us to test our unit in isolation and also control the behavior of the dependency through mocks, as shown below:

@Test
void whenDependencyInjectionIsUsed_ThenMockObjectShouldBeCreated(){
    PaymentService mockPaymentService = Mockito.mock(PaymentService.class);
    when(mockPaymentService.processPayment()).thenReturn("Online Banking");
    PaymentProcessor paymentProcessor = new PaymentProcessor(mockPaymentService);
    Assertions.assertEquals("Online Banking", paymentProcessor.processPayment());
}

However, in situations where we can’t control how dependencies are managed in the source code, especially when dependency injection isn’t an option, mockConstruction() becomes a useful tool for effectively mocking constructors.

8. Conclusion

This brief article has shown different ways to mock constructors through Mockito and PowerMock. We’ve also discussed the advantages of prioritizing dependency injection when feasible.

As always, the code is available over on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.