java 如何模拟 Spring bean 的自动装配列表?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/33302340/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 21:29:20  来源:igfitidea点击:

How to mock a autowired list of Spring beans?

javaspringunit-testingmockitoautowired

提问by Grégory Elhaimer

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.

我已经阅读了大量关于如何模拟 Spring 的 bean 及其自动装配字段的文章。但是我找不到关于自动装配的 bean 列表的任何信息。

Concrete problem

具体问题

I've a class called FormValidatorManager. This class loop through several validators which implements IFormValidator.

我有一个班级叫FormValidatorManager. 这个类循环通过几个实现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 validatorsproperty.

我想测试这门课。但我找不到模拟validators财产的方法。

What I've tried

我试过的

Since IFormValidatorsare singleton, I tried to mock several instances of these beans hoping them to be reflected in FormValidatorManager.validatorsbut without success.

由于IFormValidators是单例,我试图模拟这些 bean 的几个实例,希望它们能被反映,FormValidatorManager.validators但没有成功。

Then, I tried to create a list of IFormValidatorswhich was annotated as @Mock. By initiating the Listmanually, I was hoping initMocks()to inject the created list. That was still without success.

然后,我尝试创建一个IFormValidators注释为 @Mock. 通过List手动启动,我希望initMocks()注入创建的列表。那仍然没有成功。

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.

一个 NPE 被抛出IFormValidator.validate(),我可能会被嘲笑。不应该调用具体的实现。

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.

我试图弄清楚如何模拟自动装配的 bean 列表,同时仍然有可能模拟特定的实现。

Do you have an idea start of solution ?

你有解决方案的想法吗?

Regards

问候

回答by Grégory Elhaimer

I finally figured it out...

我终于想通了...

Sometimes, asking a question can give you a better approach to your problems :p

有时,提出问题可以让您更好地解决问题: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.

问题是我在验证器被嘲笑之前将它们链接到列表。然后验证器为空,并且MockitAnnotations.initMocks(this)在调用时无法更新任何引用。

Moreover, to avoid iterator problems on List, I had to use @Spyinstead of @Mock.

此外,为了避免 上的迭代器问题List,我不得不使用@Spy代替@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");
}