如何模拟 Spring bean 的自动装配列表?
How to mock a autowired list of Spring beans?
我已经阅读了大量关于如何模拟 Spring 的 bean 及其自动装配字段的文章。但是我找不到关于自动装配的 bean 列表的任何信息。
具体问题
我有一个 class 叫 FormValidatorManager
。这个 class 循环通过几个实现 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;
}
}
我想测试一下 class。但是我找不到模拟 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,我认为这会被嘲笑。具体实现应该不会调用。
这会导致非常糟糕的行为,因为我对此 class 的一些测试是误报,而其他测试则完全失败。
我正在尝试弄清楚如何模拟一个自动装配的 bean 列表,同时仍然有可能模拟特定的实现。
您有解决方案的想法吗?
此致
我终于想通了...
有时候,提出问题可以让您更好地解决问题 :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");
}
在处理多个 beans 列表时添加另一个答案。 Mockito 对泛型一无所知,它只是使用提供的随机列表,所以在我的情况下发生了这样的事情。
由于未正确执行 bean 注入而抛出 ClassCastException。期待 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);
}
方法 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);
}
我已经阅读了大量关于如何模拟 Spring 的 bean 及其自动装配字段的文章。但是我找不到关于自动装配的 bean 列表的任何信息。
具体问题
我有一个 class 叫 FormValidatorManager
。这个 class 循环通过几个实现 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;
}
}
我想测试一下 class。但是我找不到模拟 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,我认为这会被嘲笑。具体实现应该不会调用。
这会导致非常糟糕的行为,因为我对此 class 的一些测试是误报,而其他测试则完全失败。
我正在尝试弄清楚如何模拟一个自动装配的 bean 列表,同时仍然有可能模拟特定的实现。
您有解决方案的想法吗?
此致
我终于想通了...
有时候,提出问题可以让您更好地解决问题 :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");
}
在处理多个 beans 列表时添加另一个答案。 Mockito 对泛型一无所知,它只是使用提供的随机列表,所以在我的情况下发生了这样的事情。
由于未正确执行 bean 注入而抛出 ClassCastException。期待 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);
}
方法 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);
}