I’ve read plenty of articles about how to mock Spring’s bean and their autowired fields. But there is nothing I could find about autowired lists of beans.
Concrete problem
I’ve a class called FormValidatorManager
. This class loop through several validators which implements IFormValidator
.
@Component public class FormValidatorManager implements IValidatorManager { @Autowired private List<IFormValidator> validators; @Override public final IFieldError validate(ColumnDTO columnToValidate, String sentValue) { String loweredColName = columnToValidate.getName().toLowerCase(); IFieldError errorField = new FieldError(loweredColName); for (IEsmFormValidator validator : validators) { List<String> errrorsFound = validator.validate(columnToValidate, sentValue); //les erreurs ne doivent pas être cumulées. if(CollectionUtils.isNotEmpty(errrorsFound)){ errorField.addErrors(errrorsFound); break; } } return errorField; } }
I would like to test this class. But I can’t find a way to mock validators
property.
What I’ve tried
Since IFormValidators
are singleton, I tried to mock several instances of these beans hoping them to be reflected in FormValidatorManager.validators
but without success.
Then, I tried to create a list of IFormValidators
which was annotated as @Mock
. By initiating the List
manually, I was hoping initMocks()
to inject the created list. That was still without success.
Here is my last try:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath:spring/test-validator-context.xml"}) public class FormValidatorManagerTest { @Mock private RegexValidator regexValidator; @Mock private FormNotNullValidator notNullValidator; @Mock private FormDataTypeValidator dataValidator; @InjectMocks private FormValidatorManager validatorManager; @Mock private List<IEsmFormValidator> validators = new ArrayList<IEsmFormValidator>(); @Mock private ColumnDTO columnDTO; @Before public void init() { validators.add(notNullValidator); validators.add(regexValidator); validators.add(dataValidator); MockitoAnnotations.initMocks(this); Mockito.when(columnDTO.getTitle()).thenReturn("Mock title"); Mockito.when(columnDTO.getName()).thenReturn("Mock name"); } @Test public void testNoErrorFound(){ mockValidator(notNullValidator, new ArrayList<String>()); mockValidator(regexValidator, new ArrayList<String>()); mockValidator(dataValidator, new ArrayList<String>()); IFieldError fieldErrors = validatorManager.validate(columnDTO, "Not null value"); Assert.assertEquals(0, fieldErrors.getErrors().size()); verifyNumberOfValidateCalls(regexValidator, Mockito.atMost(1)); verifyNumberOfValidateCalls(dataValidator, Mockito.atMost(1)); verifyNumberOfValidateCalls(notNullValidator, Mockito.atMost(1)); } private void mockValidator(IFormValidator validator, List<String> listToReturn){ Mockito.when(validator.validate(Mockito.any(ColumnDTO.class), Mockito.anyString())).thenReturn( listToReturn ); } private void verifyNumberOfValidateCalls(IFormValidator validator, VerificationMode verifMode){ Mockito.verify(validator, verifMode).validate(Mockito.any(ColumnDTO.class), Mockito.anyString()); } }
An NPE is thrown in IFormValidator.validate()
which I thougth would be mocked. The concrete implementation should not be called.
This leads to a really bad behavior since some of my tests on that class are false positives while others completly fail.
I’m trying to figure out how to mock an autowired list of beans while still having the possibility to mock specific implementations.
Do you have an idea start of solution ?
Regards
Advertisement
Answer
I finally figured it out…
Sometimes, asking a question can give you a better approach to your problems :p
The problem is I was linking the validators to the list before they were mocked. The validators was then null and no reference could be updated when the MockitAnnotations.initMocks(this)
was called.
Moreover, to avoid iterator problems on List
, I had to use @Spy
instead of @Mock
.
Here is the final solution:
@Mock private EsmRegexValidator regexValidator; @Mock private EsmFormNotNullValidator notNullValidator; @Mock private EsmFormDataTypeValidator dataValidator; @InjectMocks private EsmFormValidatorManager validatorManager; @Spy private List<IEsmFormValidator> validators = new ArrayList<IEsmFormValidator>(); @Mock private ColumnDTO columnDTO; @Before public void init() { MockitoAnnotations.initMocks(this); validators.add(notNullValidator); validators.add(regexValidator); validators.add(dataValidator); Mockito.when(columnDTO.getTitle()).thenReturn("Mock title"); Mockito.when(columnDTO.getName()).thenReturn("Mock name"); }