Java 如何在SpringJUnit4ClassRunner中模拟缺少的bean定义?
我有一个Spring4JUnit测试,它应该只验证我的应用程序的一个特定部分Java 如何在SpringJUnit4ClassRunner中模拟缺少的bean定义?,java,spring,junit,spring-test,spring-4,Java,Spring,Junit,Spring Test,Spring 4,我有一个Spring4JUnit测试,它应该只验证我的应用程序的一个特定部分 @WebAppConfiguration @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:context-test.xml") @ActiveProfiles("test") public class FooControllerIntegrationTest { ... } 所以我不想配置和实例化所有那些实际上
@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:context-test.xml")
@ActiveProfiles("test")
public class FooControllerIntegrationTest {
...
}
所以我不想配置和实例化所有那些实际上不在我的测试范围内的bean。例如,我不想配置在另一个控制器中使用的bean,我不打算在这里进行测试
但是,因为我不想缩小组件扫描路径,所以我得到了“No qualification bean of type”异常:
原因:
org.springframework.beans.factory.noSuchBean定义异常:否
类型为[…]的限定bean
如果我确定这些遗漏的定义没有包含在我正在测试的功能中,那么有没有办法忽略这些遗漏的定义?如果您不缩小组件扫描范围,那么通常您将拥有所有可用于测试的bean,除了一些有条件可用的特定bean(例如,spring batch定义的bean) 在这种情况下,对我有效的一个选项是将此类依赖项和组件标记为
@Lazy
。这将确保它们仅在需要时加载。注意(取决于场景),您可能必须将@Autowired
依赖项和@Component
标记为@Lazy
如果我确信这些遗漏的定义没有涉及到我正在测试的功能中,有没有办法忽略它们
不,没有用于此目的的自动或内置机制
如果您指示Spring加载对其他bean具有强制依赖关系的bean,那么这些bean必须存在
出于测试目的,限制bean活动范围的最佳实践包括配置的模块化(例如,允许您有选择地选择加载应用程序的哪些层的水平切片)和bean定义配置文件的使用
如果您使用的是Spring Boot,那么您还可以在Spring Boot测试中使用“测试片”或@MockBean
/@SpyBean
但是,您应该记住,加载您在给定集成测试中未使用的bean通常不是一件坏事,因为您(希望如此)测试测试套件中其他测试类中实际需要这些bean的其他组件时,ApplicationContext
将只加载一次,并在不同的集成测试类中缓存
问候,
Sam(SpringTestContext框架的作者)我找到了一种自动模拟缺失bean定义的方法 核心理念是创建自己的
BeanFactory
:
public class AutoMockBeanFactory extends DefaultListableBeanFactory {
@Override
protected Map<String, Object> findAutowireCandidates(final String beanName, final Class<?> requiredType, final DependencyDescriptor descriptor) {
String mockBeanName = Introspector.decapitalize(requiredType.getSimpleName()) + "Mock";
Map<String, Object> autowireCandidates = new HashMap<>();
try {
autowireCandidates = super.findAutowireCandidates(beanName, requiredType, descriptor);
} catch (UnsatisfiedDependencyException e) {
if (e.getCause() != null && e.getCause().getCause() instanceof NoSuchBeanDefinitionException) {
mockBeanName = ((NoSuchBeanDefinitionException) e.getCause().getCause()).getBeanName();
}
this.registerBeanDefinition(mockBeanName, BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition());
}
if (autowireCandidates.isEmpty()) {
final Object mock = mock(requiredType);
autowireCandidates.put(mockBeanName, mock);
this.addSingleton(mockBeanName, mock);
}
return autowireCandidates;
}
}
与发布的OP一样,以下是用于注入任何模拟缺失bean的注释上下文:
context = new CustomAnnotationConfigApplicationContext(SpringDataJpaConfig.class);
public class CustomAnnotationConfigApplicationContext extends AnnotationConfigApplicationContext {
public CustomAnnotationConfigApplicationContext() {
super(new AutoMockBeanFactory());
}
public CustomAnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
this();
this.register(annotatedClasses);
this.refresh();
}
}
public class AutoMockBeanFactory extends DefaultListableBeanFactory {
@Override
protected Map<String, Object> findAutowireCandidates(final String beanName, final Class<?> requiredType, final DependencyDescriptor descriptor) {
String mockBeanName = Introspector.decapitalize(requiredType.getSimpleName());
Map<String, Object> autowireCandidates = new HashMap<>();
try {
autowireCandidates = super.findAutowireCandidates(beanName, requiredType, descriptor);
} catch (UnsatisfiedDependencyException e) {
if (e.getCause() != null && e.getCause().getCause() instanceof NoSuchBeanDefinitionException) {
mockBeanName = ((NoSuchBeanDefinitionException) e.getCause().getCause()).getBeanName();
}
this.registerBeanDefinition(mockBeanName, BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition());
}
if (autowireCandidates.isEmpty()) {
System.out.println("Mocking bean: " + mockBeanName);
final Object mock = Mockito.mock(requiredType);
autowireCandidates.put(mockBeanName, mock);
this.addSingleton(mockBeanName, mock);
}
return autowireCandidates;
}
}
context=新的CustomAnnotationConfigApplicationContext(SpringDataJpaConfig.class);
公共类CustomAnnotationConfigApplicationContext扩展了AnnotationConfigApplicationContext{
公共CustomAnnotationConfigApplicationContext(){
super(新的AutoMockBeanFactory());
}
公共CustomAnnotationConfigApplicationContext(类…AnnotatedClass){
这个();
这个。注册(注释类);
这个。刷新();
}
}
公共类AutoMockBeanFactory扩展了DefaultListableBeanFactory{
@凌驾
受保护映射FinDautoWirePredidates(最终字符串beanName、最终类requiredType、最终依赖描述符){
字符串mockBeanName=Introspector.decapitalize(requiredType.getSimpleName());
Map Autowire候选者=新HashMap();
试一试{
autowireCandidates=super.findAutowireCandidates(beanName、requiredType、descriptor);
}捕获(未满足的支出异常e){
如果(e.getCause()!=null&&e.getCause().getCause()实例为NoSuchBeanDefinitionException){
mockBeanName=((NoSuchBeanDefinitionException)e.getCause().getCause()).getBeanName();
}
这个.registerBeanDefinition(mockBeanName、BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition());
}
if(autowire.isEmpty()){
System.out.println(“mockingbean:+mockBeanName”);
最终对象mock=Mockito.mock(requiredType);
autowire.put(mockBeanName,mock);
this.addSingleton(mockBeanName,mock);
}
返回候选人;
}
}
但我不想在生产代码中将我的Bean标记为懒惰。这将在应用程序加载时中断预检查。当然……不过,如果您确实对使用懒惰部分的组件进行了测试,则该风险已被涵盖……无论如何,感谢您的回答。但我非常肯定有一些优雅的方法。例如,一些自定义bean loader,它可以用Mockito自动模拟缺失的依赖项。当然,没问题。我感谢你的回答!非常感谢。但我仍在考虑编写自己的测试工具,自动模拟缺失的依赖项。我至少找到了一个可行的解决方案。请检查我的自我回答:。我真的对你的意见感兴趣。这是一个非常有趣的自定义解决方案。我还没有对实现进行彻底的分析;但是,如果您只想模拟通过自动连线注入的缺少的bean(如果它确实适合您),那么无论如何……使用它!你愿意给出一个关于如何使用它的例子吗?我正试图在@DataJpaTest和@extendedwith(SpringExtension.class)@crystake的测试环境中使用它,这本身就是一个完整的例子,哪种类型(JPA)或模式(*Repo)您想要模拟的bean的数量由您决定。这在某些项目中的集成测试中非常有用,因为有些bean无法创建或不需要。
context = new CustomAnnotationConfigApplicationContext(SpringDataJpaConfig.class);
public class CustomAnnotationConfigApplicationContext extends AnnotationConfigApplicationContext {
public CustomAnnotationConfigApplicationContext() {
super(new AutoMockBeanFactory());
}
public CustomAnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
this();
this.register(annotatedClasses);
this.refresh();
}
}
public class AutoMockBeanFactory extends DefaultListableBeanFactory {
@Override
protected Map<String, Object> findAutowireCandidates(final String beanName, final Class<?> requiredType, final DependencyDescriptor descriptor) {
String mockBeanName = Introspector.decapitalize(requiredType.getSimpleName());
Map<String, Object> autowireCandidates = new HashMap<>();
try {
autowireCandidates = super.findAutowireCandidates(beanName, requiredType, descriptor);
} catch (UnsatisfiedDependencyException e) {
if (e.getCause() != null && e.getCause().getCause() instanceof NoSuchBeanDefinitionException) {
mockBeanName = ((NoSuchBeanDefinitionException) e.getCause().getCause()).getBeanName();
}
this.registerBeanDefinition(mockBeanName, BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition());
}
if (autowireCandidates.isEmpty()) {
System.out.println("Mocking bean: " + mockBeanName);
final Object mock = Mockito.mock(requiredType);
autowireCandidates.put(mockBeanName, mock);
this.addSingleton(mockBeanName, mock);
}
return autowireCandidates;
}
}