【问题标题】:How to mock a autowired list of Spring beans?如何模拟 Spring bean 的自动装配列表?
【发布时间】:2016-01-22 23:45:42
【问题描述】:

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

具体问题

我有一个名为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;
    }
}

我想测试这个类。但我找不到模拟 validators 属性的方法。

我的尝试

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

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

这是我最后一次尝试:

@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());
    }
}

IFormValidator.validate() 中抛出了一个 NPE,我认为它会被嘲笑。不应该调用具体的实现。

这会导致非常糟糕的行为,因为我对该类的一些测试是误报,而另一些则完全失败。

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

你有一个解决方案的想法开始吗?

问候

【问题讨论】:

  • 您希望@Mock@Autowired 在单个字段上如何工作?你想要它被嘲笑,还是自动连接?同时在您的测试中创建一个列表并希望 spring 检测到它永远不会发生。在你的 spring 配置而不是你的类中创建模拟,为此使用 Mockitomock 工厂方法。
  • 我实际上希望它被嘲笑,但既然它是一个弹簧豆,也不应该被自动装配吗?我实际上试图删除自动连线,但它也不起作用。更糟糕的是,验证器为空且未模拟。
  • 你可以使用@Autowired 上面的构造函数。所以在测试中你可以将验证器作为构造函数参数传递给被测试的类。
  • 我不能更改测试的类实现
  • 它要么是一个mock bean,要么是一个spring bean,不能两者兼而有之。你可以让你的 mock 自动连接,但只有当你将 Mocks 定义为 spring bean 时才会起作用。您可能想阅读olivergierke.de/2013/11/why-field-injection-is-evil 并重新考虑字段注入。我会将字段设为 final 并简单地创建一个构造函数。这样你就可以真正让它成为一个单元测试并自己构建对象并将任何你想要的东西传递给构造函数,你可以提高代码质量。

标签: java spring unit-testing mockito autowired


【解决方案1】:

我终于明白了……

有时,提出问题可以更好地解决问题:p

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

此外,为了避免List 上的迭代器问题,我不得不使用@Spy 而不是@Mock

这是最终的解决方案:

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

【讨论】:

  • 这个解决方案似乎有其局限性。处理 2 个 Bean 列表时。它没有正确注入 bean。 repositories.add(emailRepository); parser.add(smsParser);现在在实际的类代码中 - 当使用存储库时 - 它由解析器模拟组成 -.- 该死的,这是一个非常古老的答案。必须看看有没有什么新东西出现。
【解决方案2】:

在处理多个 bean 列表时添加另一个答案。 Mockito 对泛型一无所知,它只是使用提供的随机列表,所以在我的情况下发生了这样的事情。

抛出 ClassCastException 是因为 bean 注入没有正确执行。期待 SfmcImportRepository 但注入是 SfmcParser

  @Mock SfmcEmailsCsvFileParser emailParser;
  @Mock SfmcSmsCsvFileParser smsParser;
  @Mock SfmcSmsRepository smsRepository;
  @Mock SfmcEmailRepository emailRepository;

  List<SfmcImportRepository> sfmcImportRepositories = new ArrayList<>();
  List<SfmcParser> sfmcParsers = new ArrayList<>();
  SfmcFtpService service;

  @Before
  public void init() {
    sfmcImportRepositories.add(emailRepository);
    sfmcImportRepositories.add(smsRepository);
    sfmcParsers.add(smsParser);
    sfmcParsers.add(emailParser);
    service = new SfmcFtpService(sfmcImportRepositories, sfmcParsers);
  }

【讨论】:

    【解决方案3】:

    方法 initMocks 在最近的版本中被弃用,不再需要:

    @Mock
    private SomeTxHandler1 txHandler1;
    @Mock
    private SomeTxHandler2 txHandler2;
    
    @Spy
    private final List<TxHandler> txHandlers = new ArrayList<>();
    
    @Spy // if you want to mock your service's methods
    @InjectMocks
    private MyService myService;
    
    @BeforeEach
    public void init() {
        lenient().when(txHandler1.accept(...)).thenReturn(true);
        txHandlers.add(txHandler1);
        lenient().when(txHandler2.accept(...)).thenReturn(true);
        txHandlers.add(txHandler2);
    }
    

    【讨论】:

      猜你喜欢
      • 2021-09-02
      • 1970-01-01
      • 2012-08-11
      • 1970-01-01
      • 2013-10-18
      • 2016-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多