Injecting @Autowired Private Fields in Unit Testing: Best Practices with Mockito and Spring

Dec 01, 2025 · Programming · 29 views · 7.8

Keywords: Unit Testing | @Autowired | Mockito | Spring Framework | Dependency Injection

Abstract: This article delves into unit testing private fields annotated with @Autowired in the Spring framework. Focusing on the MyLauncher class that depends on MyService, it details the recommended approach using MockitoJUnitRunner and @InjectMocks annotations, which automatically inject mock objects without manual setters or extra XML configuration files. Additionally, it covers alternative methods like ReflectionTestUtils and refactoring to constructor injection. Through code examples and step-by-step analysis, the article helps developers grasp core concepts for efficient and maintainable test code.

Introduction

In Java applications built with the Spring framework, dependency injection (DI) via the @Autowired annotation simplifies component coupling but poses challenges for unit testing, especially when dependency fields are private and lack public accessors. This article uses the MyLauncher class as a case study, where MyService is injected as a private field with @Autowired, to explore how to inject mock objects in JUnit tests without modifying production code or relying on external configuration files.

Core Problem Analysis

The MyLauncher class is defined as follows:

@Component
public class MyLauncher {
    @Autowired
    MyService myService;

    // other methods
}

In testing, directly instantiating MyLauncher (e.g., private MyLauncher myLauncher = new MyLauncher();) leaves the myService field null, as Spring's autowiring mechanism doesn't activate in test environments. This hinders isolated testing because MyService may involve external dependencies like databases or network services. Thus, a method is needed to inject mock objects into private fields to simulate MyService's behavior.

Recommended Solution: Using MockitoJUnitRunner and @InjectMocks

Based on best practices, the recommended approach combines the Mockito framework with JUnit's @RunWith annotation. This leverages MockitoJUnitRunner to automatically handle mock creation and injection, as shown in this code example:

@RunWith(MockitoJUnitRunner.class)
public class MyLauncherTest {
    @InjectMocks
    private MyLauncher myLauncher;

    @Mock
    private MyService myService;

    @Test
    public void someTest() {
        // Configure mock behavior
        Mockito.when(myService.someMethod()).thenReturn("mocked result");
        // Execute test logic
        myLauncher.launch();
        // Verify interactions
        Mockito.verify(myService).someMethod();
    }
}

In this example, the @Mock annotation creates a mock instance of MyService, and @InjectMocks automatically injects this mock into the private field myService of myLauncher. MockitoJUnitRunner initializes and manages these annotations, eliminating the need for manual constructor calls or setter methods. This approach avoids maintaining separate XML configuration files and keeps test code concise and readable.

Alternative Approach: Using ReflectionTestUtils

If you prefer to avoid special JUnit Runners or desire more transparent control, Spring's ReflectionTestUtils utility class can be used. This method sets private field values directly via reflection, as demonstrated below:

public class MyLauncherTest {
    private MyLauncher myLauncher = new MyLauncher();
    private MyService myService = Mockito.mock(MyService.class);

    @Before
    public void setUp() {
        ReflectionTestUtils.setField(myLauncher, "myService", myService);
    }

    @Test
    public void someTest() {
        // Test logic
    }
}

The ReflectionTestUtils.setField method takes three parameters: the target object, field name, and value to inject. It internally handles accessibility settings, simplifying reflection operations. For non-Spring projects, a custom utility method can be implemented, but using Spring's tool reduces code duplication.

Refactoring Strategy: Adopting Constructor Injection

Another long-term solution is to refactor the MyLauncher class to use constructor injection instead of field injection. This not only improves testability but also enhances code maintainability and immutability. The refactored code is as follows:

@Component
public class MyLauncher {
    private MyService myService;

    @Autowired
    public MyLauncher(MyService myService) {
        this.myService = myService;
    }

    // other methods
}

In tests, mock objects or stubs can be injected directly via the constructor, without relying on reflection or Mockito's magic:

public class MyLauncherTest {
    private MyLauncher myLauncher;
    private MyService myService = Mockito.mock(MyService.class);

    @Before
    public void setUp() {
        myLauncher = new MyLauncher(myService);
    }

    @Test
    public void someTest() {
        // Test logic
    }
}

This method aligns with best practices in dependency injection, making dependencies explicit and easier to understand and test. If MyService is an interface, test stubs (e.g., MyServiceStub) can replace mocking frameworks, reducing external dependencies.

Conclusion and Best Practice Recommendations

When injecting @Autowired private fields in unit testing, prioritize using MockitoJUnitRunner with @InjectMocks, as it automates mock injection, minimizes boilerplate code, and integrates well with the Spring ecosystem. For simpler scenarios or greater control, ReflectionTestUtils offers a lightweight alternative. In the long run, consider refactoring to constructor injection to boost testability and design quality. Regardless of the method chosen, ensure test code is maintainable, avoid over-reliance on magical annotations or reflection, and maintain test transparency and reliability.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.