Course – LS – All

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

>> CHECK OUT THE COURSE

1. Overview

When writing unit tests, sometimes we’ll encounter a situation where it can be useful to return a mock when we construct a new object. For example, when testing legacy code with tightly coupled object dependencies.

In this tutorial, we’ll take a look at a relatively new feature of Mockito that lets us generate mocks on constructor invocations.

To learn more about testing with Mockito, check out our comprehensive Mockito series.

2. Dependencies

First, we’ll need to add the mockito dependency to our project:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>5.11.0</version>
    <scope>test</scope>
</dependency>

If we’re using a version of Mockito inferior to version 5, then we’ll also need to explicitly add Mockito’s mock maker inline dependency:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>5.2.0</version>
    <scope>test</scope>
</dependency>

3. A Quick Word on Mocking Constructor Calls

Generally speaking, some might say that when writing clean object-orientated code, we shouldn’t need to return a mock instance when creating an object. This could typically hint at a design issue or code smell in our application.

Why? First, a class depending on several concrete implementations could be tightly coupled, and second, this nearly always leads to code that is difficult to test. Ideally, a class should not be responsible for obtaining its dependencies, and if possible, they should be externally injected.

So, it’s always worth investigating if we can refactor our code to make it more testable. Of course, this isn’t always possible, and sometimes, we need to temporarily replace the behavior of a class after constructing it.

This might be particularly useful in several situations:

  • Testing difficult-to-reach scenarios – particularly if our class under test has a complex object hierarchy
  • Testing interactions with external libraries or frameworks
  • Working with legacy code

In the following sections, we’ll see how we can use Mockito’s MockConstruction to combat some of these situations in order to control the creation of objects and specify how they should behave when constructed.

4. Mocking Constructors

Let’s start by creating a simple Fruit class, which will be the focus of our first unit test:

public class Fruit {

    public String getName() {
        return "Apple";
    }

    public String getColour() {
        return "Red";
    }
}

Now let’s go ahead and write our test where we mock the constructor call made to our Fruit class:

@Test
void givenMockedContructor_whenFruitCreated_thenMockIsReturned() {
    assertEquals("Apple", new Fruit().getName());
    assertEquals("Red", new Fruit().getColour());

    try (MockedConstruction<Fruit> mock = mockConstruction(Fruit.class)) {
        Fruit fruit = new Fruit();
        when(fruit.getName()).thenReturn("Banana");
        when(fruit.getColour()).thenReturn("Yellow");

        assertEquals("Banana", fruit.getName());
        assertEquals("Yellow", fruit.getColour());

        List<Fruit> constructed = mock.constructed();
        assertEquals(1, constructed.size());
    }
}

In our example, we start by checking that a real Fruit object returns the desired values.

Now, to make mocking object constructions possible, we’ll use the Mockito.mockConstruction() method. This method takes a non-abstract Java class for the constructions we’re about to mock. In this case, a Fruit class.

We define this within a try-with-resources block. This means that when our code calls the constructor of a Fruit object inside the try statement, it returns a mock object. We should note that the constructor won’t be mocked by Mockito outside our scoped block.

This is a particularly nice feature since it ensures that our mock remains temporary. As we know, if we’re playing around with mock constructor calls during our test runs, this could likely lead to adverse effects on our test results due to the concurrent and sequential nature of running tests.

5. Mocking Constructors Inside Another Class

A more realistic scenario is when we have a class under test, which creates some objects inside that we would like to mock.

Typically, inside the constructor of our class under test, we might create instances of new objects that we would like to mock from our tests. In this example, we’ll see how we can do this.

Let’s start by defining a simple coffee-making application:

public class CoffeeMachine {

    private Grinder grinder;
    private WaterTank tank;

    public CoffeeMachine() {
        this.grinder = new Grinder();
        this.tank = new WaterTank();
    }

    public String makeCoffee() {
        String type = this.tank.isEspresso() ? "Espresso" : "Americano";
        return String.format("Finished making a delicious %s made with %s beans", type, this.grinder.getBeans());
    }
}

Next, we define the Grinder class:

public class Grinder {

    private String beans;

    public Grinder() {
        this.beans = "Guatemalan";
    }

    public String getBeans() {
        return beans;
    }

    public void setBeans(String beans) {
        this.beans = beans;
    }
}

Finally, we add the WaterTank class:

public class WaterTank {

    private int mils;

    public WaterTank() {
        this.mils = 25;
    }

    public boolean isEspresso() {
        return getMils() < 50;
    }
    
    //Getters and Setters
}

In this trivial example, our CoffeeMachine creates a grinder and tank at construction time. We have one method, makeCoffee(), which prints out a message about the brewed coffee.

Now, we can go ahead and write a couple of tests:

@Test
 void givenNoMockedContructor_whenCoffeeMade_thenRealDependencyReturned() {
    CoffeeMachine machine = new CoffeeMachine();
    assertEquals("Finished making a delicious Espresso made with Guatemalan beans", machine.makeCoffee());
}

In this first test, we’re checking that when we don’t use MockedConstruction, our coffee machine returns real dependencies inside.

Now let’s see how we can return mocks for those dependencies:

@Test
void givenMockedContructor_whenCoffeeMade_thenMockDependencyReturned() {
    try (MockedConstruction<WaterTank> mockTank = mockConstruction(WaterTank.class); 
      MockedConstruction<Grinder> mockGrinder = mockConstruction(Grinder.class)) {

        CoffeeMachine machine = new CoffeeMachine();

        WaterTank tank = mockTank.constructed().get(0);
        Grinder grinder = mockGrinder.constructed().get(0);

        when(tank.isEspresso()).thenReturn(false);
        when(grinder.getBeans()).thenReturn("Peruvian");

        assertEquals("Finished making a delicious Americano made with Peruvian beans", machine.makeCoffee());
    }
}

In this test, we use mockConstruction to return mocks instances when we call the constructors of  Grinder and WaterTank. Then, we specify the expectations of these mocks using standard when notation.

This time around, when we run our test, Mockito ensures that the constructors of Grinder and WaterTank return the mocked instances with the specified behavior, allowing us to test the makeCoffee method in isolation.

6. Dealing with Constructor Arguments

Another common use case is to be able to deal with a constructor which takes an argument.

Thankfully, mockedConstruction provides a mechanism allowing us to access the arguments passed to the constructor:

Let’s add a new constructor to our WaterTank:

public WaterTank(int mils) {
    this.mils = mils;
}

Likewise, let’s also add a new constructor to our Coffee application:

public CoffeeMachine(int mils) {
    this.grinder = new Grinder();
    this.tank = new WaterTank(mils);
}

Finally, we can add another test:

@Test
void givenMockedContructorWithArgument_whenCoffeeMade_thenMockDependencyReturned() {
    try (MockedConstruction<WaterTank> mockTank = mockConstruction(WaterTank.class, 
      (mock, context) -> {
          int mils = (int) context.arguments().get(0);
          when(mock.getMils()).thenReturn(mils);
      }); 
      MockedConstruction<Grinder> mockGrinder = mockConstruction(Grinder.class)) {
          CoffeeMachine machine = new CoffeeMachine(100);

          Grinder grinder = mockGrinder.constructed().get(0);
          when(grinder.getBeans()).thenReturn("Kenyan");
          assertEquals("Finished making a delicious Americano made with Kenyan beans", machine.makeCoffee());
        }
    }

This time around, we use a lambda expression to handle the WaterTank constructor with arguments. The lambda receives the mock instance and the construction context, allowing us to access the arguments passed to the constructor.

We can then use these arguments to set up the desired behavior for the getMils method.

7. Changing the Default Mocking Behaviour

It’s important to note that for methods, we don’t stub a mock return null by default. We can take our Fruit example one step further and let the mock behave like a real Fruit instance:

@Test
void givenMockedContructorWithNewDefaultAnswer_whenFruitCreated_thenRealMethodInvoked() {
    try (MockedConstruction<Fruit> mock = mockConstruction(Fruit.class, withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS))) {

        Fruit fruit = new Fruit();

        assertEquals("Apple", fruit.getName());
        assertEquals("Red", fruit.getColour());
    }
}

This time, we pass an extra parameter MockSettings to the mockConstruction method to tell it to create a mock that will behave like a real Fruit instance for methods that we didn’t stub.

8. Conclusion

In this quick article, we’ve seen a couple of examples of how we can use Mockito to mock constructor calls. To summarise, Mockito provides a graceful solution to generate mocks on constructor invocations within the current thread and a user-defined scope.

As always, the full source code of the article 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.