Understanding @InjectMocks for private field mocking

Photo by Jay Mantri on Unsplash

Understanding @InjectMocks for private field mocking

Recently I've been working on getting unit tests set up for a decent chunk of java code. The existing tests were more of a placeholder until the team I work with could set up more rigorous testing. The majority of the code hasn't updated very much but there are some hopeful changes on the horizon and I felt I should revisit the tests before we start restructuring and recoding with new libraries.

As I began to refresh my memory with our unit tests I realized something; we had already updated the code to consolidate our database connections into its own class and a good portion of the unit tests were related to testing those connections. Now, all of those connections were being handled in a common service which had its own unit tests rendering the existing tests obsolete.

A couple of the test files had been set up for classes that were early adopters of the new service dependency and would provide a good launching point for getting the rest in line. To be honest I was a little rusty on the semantics as I spend most of my time doing programming in AngularJS* so I needed to refresh my memory on all of the annotations that I was seeing.

*Yeah we are still using AngularJS and not the newer Angular framework but there are plans to update this year. That's partially the reason I've been pushing to get more unit tests in place in order to capture how things should work before breaking everything and piecing it back together. The joys of legacy code :)

Let me start off with a sample of what I was working with at the time. I'll be up front and say that the examples that I'll share probably don't follow current best practices but it does provide some interesting challenges and might be helpful example for others that are running into a similar problem.

public class MyQueryHandler {
    private DBService dbService;
    private String id;
    private String value;

    public MyQueryHandler(String id, String value) {
        this.id = id;
        this.value = value;
        this.dbService = new DBService();
    }

    public String execute() { ... }
}

The DBService is our custom class that handles creating database connections as well as running queries and returning the results. As you can see this service isn't provided as a parameter to the constructor and the values that provided are strings which are used in the database query. This becomes an important note in regards to testing and using @InjectMocks annotation.

For testing I am using junit v4.x and my initial take on testing the example class was something like this.

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;

import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@RunWith(MockitoJunitRunner.class)
public class MyQueryHandlerTest {
@Test
    public void executeShouldReturnResultFromQuery() {
        // Mock the dependent service class
        DBService dbServiceMock = mock(DBService.class);

        // Create a new instance of the query handler
        MyQueryHandler handler = new MyQueryHandler("id_001", "oolong tea");
        // Assign the mocked service to the handler using a setter method
        handler.setDBService(dbServiceMock);

        // Stub out the runQuery method on our mock
        when(dbServiceMock.runQuery(anyString(), anyString())).thenReturn("mocked result");

        // Grab the result
        String result = handler.execute();
        // Verify that the mock ran the query function
        verify(dbServiceMock).runQuery(eq("id_001"), eq("oolong tea"));
        // See if the result matched the expected mocked value
        assertEquals("mocked result", result);
     }
}

Now the eagle-eyed reader might notice I have a method setDBService(...) in the test. Here is the implementation in the class being tested.

public void setDBService(DBService dbService) {
    this.dbService = dbService;
}

This was added to the original class being tested as a way to make sure the service being used in the test was a mocked version of the real class. It is a straightforward setter that wasn't originally included in the handler but worked for testing purposes. While it was a valid way to set a mocked value I personally didn't want to add a new function to every handler just to be able to run unit tests.

Next up I investigated if there was a way to do this using mockito's annotations to act as a shorthand for setting up and assigning the mocked service. This is where the @InjectMocks comes into play.

@InjectMocks can be used to annotate our class object in the test and acts as an indicator to try injecting our mocked dependencies. The trick with this is @InjectMocks tries to connect the mocks with the respective object in three different ways: constructor injection, setter injection, or private field injection. Let's take a look at each of these and see what they would look like with our example.

Constructor Injection happens when the mocked object is found in the constructor parameters. Note: mockito will always try to use the largest constructor available and ignore the others in this case

public class MyQueryHandler {
    private DBService dbService;

    public MyQueryHandler(DBService dbService) {
        this.dbService = dbService;
    }
}

Setter Injection happens when there is an associated setter method present and the constructor is empty of parameters.

public class MyQueryHandler {
    private DBService dbService;

    public MyQueryHandler() { }

    public setDBService(DBService dbService) {
        this.dbService = dbService;
    }
}

Private Field Injection happens when neither of the preceding options are available and will tie the mock to a private field directly. Note: It is important that the mock name matches the private field name in this case.

public class MyQueryHandler {
    private DBService dbService;

    public MyQueryHandler() { }
}

The first couple tries I attempted usually resulted in the private field not being mocked properly as I didn't realize that it will only attempt this if there is a no-arg constructor and no setter methods. In my original class we are accepting string parameters but mockito will see that and attempt to use constructor injection and subsequently fail to set up the mocked object since it isn't present in provided parameters and String values cannot be mocked. I incorrectly thought that it would then progress to the next option but this isn't the case as mockito will just stop here.

To illustrate the point the following will not work for mocking the private field.

// MyQueryHandler.java
public class MyQueryHandler {
    private DBService dbService;
    private String id;
    private String value;

    public MyQueryHandler(String id, String value) {
        this.id = id;
        this.value = value;
        this.dbService = new dbService();
    }
}

// MyQueryHandlerTest.java
@RunWith(MockitoJunitRunner.class)
public class MyQueryHandlerTest {
    @Mock private DBService dbService;

    // Tell Mockito to inject our mocked DBService 
    @InjectMocks private MyQueryHandler handler;

    @Test
    public void executeShouldReturnResultFromQuery() {
       ...
     }
}

Because the constructor takes parameters mockito will attempt the constructor injection, fail to assign the mocked object, and silently go on its merry way while our class uses the real service during the test. So in my case I determined that I would need to use a more manual setup in order to properly mock the existing class.

In my search for mocking private variables without using @InjectMocks, I kept coming across articles about using setters or constructor injection as well as using features in newer versions of junit or other libraries we weren't using. After a little searching I came across a nice tutorial about using Mockito and more specifically using spy and reflection.

I haven't delved too deeply into the the underlying details but Java has a language feature called reflect that can be used to interact with a class and its fields. This ability combined with the @Spy notation allowed me to access the private field and manually set up the mocked dependency in my tests.

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;

import java.lang.reflect.Field;

@RunWith(MockitoJunitRunner.class)
public class MyQueryHandlerTest {
    @Mock 
    private DBService dbServiceMock;

    // Tell Mockito to spy on our query handler 
    @Spy
    private MyQueryHandler handler = new MyQueryHandler("id_001", "oolong_tea");

    @Before
    public void setup() throws NoSuchFieldException, IllegalAccessException {
        // Retrieve the private field using reflect
        Field dbServiceField = MyQueryHandler.class.getDeclaredField("dbService");
        // Set the field to be accessible so we can assign the mock
        dbServiceField.setAccessible(true);
        // set the mocked value to the field of the handler
        dbServiceField.set(handler, dbServiceMock);
        // Set up our stub for runQuery - a service method called inside MyQueryHandler
        when(dbServiceMock.runQuery(anyString(),anyString()).thenReturn("mocked");
    }

    @Test
    public void executeShouldReturnResultFromQuery()  {
        // Run execute and get the result
        String result = handler.execute();

        // Verify the stubbed method on our mock was called
        verify(dbServiceMock).runQuery(eq("id_001"),eq("oolong_tea"));

        // Assert that the expected result matches the real result
        assertEquals("mocked", result);
     }
}

With this I was able to successfully mock the dependent service, stub out the necessary methods, and control the results so I can have a more isolated test which is exactly what I was trying to accomplish.

I do want to point out a couple more things about this final setup. First is the setup of the @Spy on the class I was testing.

@Spy
private MyQueryHandler handler = new MyQueryHandler("id_001","oolong_tea");

Notice that I went ahead and called the constructor with the parameters in this declaration? This was a necessary step for me since the @Spy notation will by default look for an no-arg constructor to call when setting up the spy. I didn't have one in our class so this allowed me to explicitly tell what constructor I wanted to use in this instance. Second is my switch over to using the @Mock annotation vs the more manual DBService dbServiceMock = mock(DBService.class); that was shown in the original setup of the test. @Mock works as a shorthand to achieve the same result with less code.

I'm sure there is a better way to setup the classes that are being tested, but ultimately I achieved my goal of being able to set up tests without having to modify the existing code. I'll definitely be revisiting this to make things better but in the meantime I hope that what I learned and discovered along the way can help others on their journey.