Injecting a String property with @InjectMocks

JavaSpringUnit TestingDependency InjectionMockito

Java Problem Overview


I have a Spring MVC @Controller with this constructor:

@Autowired
public AbcController(XyzService xyzService, @Value("${my.property}") String myProperty) {/*...*/}

I want to write a standalone unit test for this Controller:

@RunWith(MockitoJUnitRunner.class)
public class AbcControllerTest {

    @Mock
    private XyzService mockXyzService;

    private String myProperty = "my property value";

    @InjectMocks
    private AbcController controllerUnderTest;

    /* tests */
}

Is there any way to get @InjectMocks to inject my String property? I know I can't mock a String since it's immutable, but can I just inject a normal String here?

@InjectMocks injects a null by default in this case. @Mock understandably throws an exception if I put it on myProperty. Is there another annotation I've missed that just means "inject this exact object rather than a Mock of it"?

Java Solutions


Solution 1 - Java

You can't do this with Mockito, but Apache Commons actually has a way to do this using one of its built in utilities. You can put this in a function in JUnit that is run after Mockito injects the rest of the mocks but before your test cases run, like this:

@InjectMocks
MyClass myClass;

@Before
public void before() throws Exception {
    FieldUtils.writeField(myClass, "fieldName", fieldValue, true);
}

Solution 2 - Java

You cannot do this with Mockito, because, as you mentioned yourself, a String is final and cannot be mocked.

There is a @Spy annotation which works on real objects, but it has the same limitations as @Mock, thus you cannot spy on a String.

There is no annotation to tell Mockito to just inject that value without doing any mocking or spying. It would be a good feature, though. Perhaps suggest it at the Mockito Github repository.

You will have to manually instantiate your controller if you don't want to change your code.

The only way to have a pure annotation based test is to refactor the controller. It can use a custom object that just contains that one property, or perhaps a configuration class with multiple properties.

@Component
public class MyProperty {
    
    @Value("${my.property}")
    private String myProperty;
    
    ...
}

This can be injected into the controller.

@Autowired
public AbcController(XyzService xyzService, MyProperty myProperty) { 
    ... 
}

You can mock and inject this then.

@RunWith(MockitoJUnitRunner.class)
public class AbcControllerTest {

    @Mock
    private XyzService mockXyzService;

    @Mock
    private MyProperty myProperty;

    @InjectMocks
    private AbcController controllerUnderTest;

    @Before
    public void setUp(){
        when(myProperty.get()).thenReturn("my property value");
    }

    /* tests */
}

This is not pretty straight forward, but at least you will be able to have a pure annotation based test with a little bit of stubbing.

Solution 3 - Java

Since you're using Spring, you can use the org.springframework.test.util.ReflectionTestUtils from the spring-test module. It neatly wraps setting a field on a object or a static field on a class (along with other utility methods).

@RunWith(MockitoJUnitRunner.class)
public class AbcControllerTest {

    @Mock
    private XyzService mockXyzService;

    @InjectMocks
    private AbcController controllerUnderTest;

    @Before
    public void setUp() {
        ReflectionTestUtils.setField(controllerUnderTest, "myProperty", 
               "String you want to inject");
    }

    /* tests */
}

Solution 4 - Java

Just don't use @InjectMocks in that case.

do:

@Before
public void setup() {
 controllerUnderTest = new AbcController(mockXyzService, "my property value");
}

Solution 5 - Java

Solution is simple: You should put constructor injection for the object type while for primitive/final dependencies you can simply use setter injection and that'll be fine for this scenario.

So this:

public AbcController(XyzService xyzService, @Value("${my.property}") String myProperty) {/*...*/}

Would be changed to:

@Autowired
public AbcController(XyzService xyzService) {/*...*/}

@Autowired
public setMyProperty(@Value("${my.property}") String myProperty){/*...*/}

And the @Mock injections in test would be as simple as:

@Mock
private XyzService xyzService;

@InjectMocks
private AbcController abcController;

@BeforeMethod
public void setup(){
    MockitoAnnotations.initMocks(this);
    abcController.setMyProperty("new property");
}

And that'll be enough. Going for Reflections is not advisable!

PLEASE AVOID THE USAGE OF REFLECTIONS IN PRODUCTION CODE AS MUCH AS POSSIBLE!!

For the solution of Jan Groot I must remind you that it will become very nasty since you will have to remove all the @Mock and even @InjectMocks and would have to initialize and then inject them manually which for 2 dependencies sound easy but for 7 dependencies the code becomes a nightmare (see below).

private XyzService xyzService;
private AbcController abcController;

@BeforeMethod
public void setup(){ // NIGHTMARE WHEN MORE DEPENDENCIES ARE MOCKED!
    xyzService = Mockito.mock(XyzService.class);
    abcController = new AbcController(xyzService, "new property");
}

Solution 6 - Java

What you can use is this : org.mockito.internal.util.reflection.Whitebox

  1. Refactor your "AbcController" class constructor

  2. In your Test class "before" method, use Whitebox.setInternalState method to specify whatever string you want

     @Before
     public void setUp(){
         Whitebox.setInternalState(controllerUnderTest, "myProperty", "The string that you want"); }
    

Solution 7 - Java

If you want to have no change in your code then use ReflectionTestUtils.setField method https://stackoverflow.com/questions/23162777/how-do-i-mock-an-autowired-value-field-in-spring-with-mockito

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSam JonesView Question on Stackoverflow
Solution 1 - JavaJacob BeasleyView Answer on Stackoverflow
Solution 2 - JavaTom VerelstView Answer on Stackoverflow
Solution 3 - JavajebeaudetView Answer on Stackoverflow
Solution 4 - JavaJan GrootView Answer on Stackoverflow
Solution 5 - JavaShayan AhmadView Answer on Stackoverflow
Solution 6 - JavaAbhinav GangulyView Answer on Stackoverflow
Solution 7 - JavaJaison VargheseView Answer on Stackoverflow