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
You have three choices here: Do actual Spring autowiring in your tests. Use injection methods that can legitimately be performed by your tests (constructor parameters, public setters, public fields - in order of preference) Use reflection to inject your mocks.
Dependency injection is very powerful feature of Inversion of Control containers like Spring and EJB. It is always good idea to encapsulate injected values into private fields. But encapsulation of autowired fields decreases testability. I like the way how Mockito solved this problem to mock autowired fields.
Java collections such as list, set, map, array are injected using the @Autowired annotation. Collections commonly used, such as ArrayList, HashMap, HashTable, HashSet, TreeHashMap, can be automatically wired using @Autowired on the spring boot.
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"); }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With