How to mock a autowired list of Spring beans?

Grégory Elhaimer picture Grégory Elhaimer · Oct 23, 2015 · Viewed 12.2k times · Source

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

Answer

Gr&#233;gory Elhaimer picture Grégory Elhaimer · Oct 23, 2015

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");
}