Robert Mason
Robert Mason

Reputation: 181

Mockito @InjectMocks not assigned a @Spy to the correct field

I have a simple test case with a spied List of Mocked Objects that I then Inject into the class being tested

@Spy List<ValidateRule> ruleServices = new ArrayList<>();

@Mock
private EvaluateGroupType evaluateGroupType;

@Mock
private ValidateServiceRule validateServiceRule;

@InjectMocks
private ValidateRulesService validateRulesService;

@Before
public void init() throws Exception {
    initMocks(this);
}

However, in the ValidateRulesService class the list is being injected to the wrong list.

List<Integer> demonstrationList = new ArrayList<>();

@Autowired
private List<ValidateRule> ruleServices;

I have also tried to inject it as a using Constructor injection and here the results are that the values are being Injected twice

List<Integer> demonstrationList = new ArrayList<>();

final private List<ValidateRule> ruleServices;

@Autowired
public ValidateRulesService(List<ValidateRule> ruleServices) {
    this.ruleServices = ruleServices;
}

enter image description here

I'm not expecting DemonstationList to have any values in either circumstance. As it doesn't have the same name or is of the same type as rulesService based on what I have read in the docs for @injectmocks.

Am i doing something wrong here, or is this a quirk of Mockito?

Upvotes: 2

Views: 4429

Answers (2)

Dimitri Mestdagh
Dimitri Mestdagh

Reputation: 44665

There are two things at play here. First of all, generics do not exist at runtime, so basically Mockito sees both List instances and then should pick one. The second issue is that your field is declared as final, which Mockito will skip when injecting mocks/spies.

With Mockito 1.x (this is the default when using Spring boot 1.x), you can't change this behaviour as far as I'm aware, so the only solution is to inject the fields by yourself in a @SetUp method:

private ValidateRulesService validateRulesService;
@Spy
private List<ValidateRule> ruleServices = new ArrayList<>();

@Before
public void setUp() {
    // Inject the mocks/spies by yourself
    validateRulesService = new ValidateRulesService(ruleServices);
}

This is also mentioned in Robert Mason's answer.

With Mockito 2.x on the other hand, it will prioritize fields using the same field name as the spy/mock, as you can see in the documentation:

Field injection; mocks will first be resolved by type (if a single type match injection will happen regardless of the name), then, if there is several property of the same type, by the match of the field name and the mock name.

And also:

Note 1: If you have fields with the same type (or same erasure), it's better to name all @Mock annotated fields with the matching fields, otherwise Mockito might get confused and injection won't happen.

However, be aware that if you use Mockito 2, it will ignore final fields when it injects the mocks/spies:

The same stands for setters or fields, they can be declared with private visibility, Mockito will see them through reflection. However fields that are static or final will be ignored.

(Emphasis is my own)

So, if you use Mockito 2.x, it should correctly inject the ruleServices if you remove the final keyword.

Upvotes: 2

Robert Mason
Robert Mason

Reputation: 181

I managed to get this to correctly mock the correct list by altering the constructor injection implementation

The test

@Spy
List<ValidateRule> ruleServices = new ArrayList<>();

@Mock
private EvaluateGroupType evaluateGroupType;

@Mock
private ValidateServiceRule validateServiceRule;

private ValidateRulesService validateRulesService;

@Before
public void init() throws Exception {
    initMocks(this);
    ruleServices = Arrays.asList(evaluateGroupType, validateServiceRule);
    validateRulesService = new ValidateRulesService(ruleServices);
}

The Class being tested

private List<ValidateRule> ruleServices;
private List<ResponseTo> responseToList = new ArrayList<>();

@Autowired
public ValidateRulesService(List<ValidateRule> ruleServices) {
    this.ruleServices = ruleServices;
}

Upvotes: 1

Related Questions