如何模拟 SpringJUnit4ClassRunner 中缺少的 bean 定义?
How to mock absent bean definitions in SpringJUnit4ClassRunner?
我有一个 Spring 4 JUnit 测试,它应该只验证我的应用程序的特定部分。
@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:context-test.xml")
@ActiveProfiles("test")
public class FooControllerIntegrationTest {
...
}
所以我不想配置和实例化所有那些实际上不涉及我的测试范围的bean。例如,我不想配置在我不打算在这里测试的另一个控制器中使用的 bean。
但是,因为我不想缩小组件扫描路径,所以我得到 "No qualifying bean of type" 异常:
Caused by:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
qualifying bean of type [...
如果我确定它们不涉及我正在测试的功能,有什么方法可以忽略这些遗漏的定义吗?
如果您不缩小 component-scan,那么通常您会拥有所有可用于测试的 beans,除了一些有条件可用的特定 beans(例如 spring-batch 定义的 beans)
在这种情况下,对我有用的一种选择是将此类依赖项和组件标记为 @Lazy
。这将确保它们只在需要时加载。请注意(取决于场景)您可能必须将 @Autowired
依赖项和 @Component
标记为 @Lazy
Is any way how to ignore such missed definitions if I certainly sure that they aren't involved into the functionality I am testing?
不,没有用于此目的的自动化或built-in机制。
如果您要指示 Spring 加载对其他 bean 具有强制依赖性的 bean,则那些其他 bean 必须存在。
出于测试目的,限制哪些 bean 处于活动状态的范围的最佳做法包括配置的模块化(例如,水平切片允许您有选择地选择加载应用程序的哪些层)和 bean 的使用定义配置文件。
如果您正在使用 Spring 启动,您还可以在 Spring 启动测试中使用 "testing slices" 或 @MockBean
/@SpyBean
。
但是,您应该记住,在给定的集成测试中加载您未使用的 bean 通常不是一件坏事,因为您(希望)正在测试实际上需要这些 bean 的其他组件在您的测试套件中测试 类,然后 ApplicationContext
将仅加载一次并 缓存 跨不同的集成测试 类.
此致,
Sam(Spring TestContext Framework 的作者)
我找到了一种自动模拟缺少的 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;
}
}
它也应该通过创建自己的 AbstractContextLoader
实现来注册,基于 GenericXmlWebContextLoader
。不幸的是,后者有一个 final
loadContext(MergedContextConfiguration mergedConfig)
方法,因此需要完全复制其实现(比如 class AutoMockGenericXmlWebContextLoader
),但有一个区别:
GenericWebApplicationContext context =
new GenericWebApplicationContext(new AutoMockBeanFactory());
不能在测试中使用:
@ContextConfiguration(
value = "classpath:context-test.xml",
loader = AutoMockGenericXmlWebContextLoader.class)
就像发布的 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;
}
}
我有一个 Spring 4 JUnit 测试,它应该只验证我的应用程序的特定部分。
@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:context-test.xml")
@ActiveProfiles("test")
public class FooControllerIntegrationTest {
...
}
所以我不想配置和实例化所有那些实际上不涉及我的测试范围的bean。例如,我不想配置在我不打算在这里测试的另一个控制器中使用的 bean。
但是,因为我不想缩小组件扫描路径,所以我得到 "No qualifying bean of type" 异常:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [...
如果我确定它们不涉及我正在测试的功能,有什么方法可以忽略这些遗漏的定义吗?
如果您不缩小 component-scan,那么通常您会拥有所有可用于测试的 beans,除了一些有条件可用的特定 beans(例如 spring-batch 定义的 beans)
在这种情况下,对我有用的一种选择是将此类依赖项和组件标记为 @Lazy
。这将确保它们只在需要时加载。请注意(取决于场景)您可能必须将 @Autowired
依赖项和 @Component
标记为 @Lazy
Is any way how to ignore such missed definitions if I certainly sure that they aren't involved into the functionality I am testing?
不,没有用于此目的的自动化或built-in机制。
如果您要指示 Spring 加载对其他 bean 具有强制依赖性的 bean,则那些其他 bean 必须存在。
出于测试目的,限制哪些 bean 处于活动状态的范围的最佳做法包括配置的模块化(例如,水平切片允许您有选择地选择加载应用程序的哪些层)和 bean 的使用定义配置文件。
如果您正在使用 Spring 启动,您还可以在 Spring 启动测试中使用 "testing slices" 或 @MockBean
/@SpyBean
。
但是,您应该记住,在给定的集成测试中加载您未使用的 bean 通常不是一件坏事,因为您(希望)正在测试实际上需要这些 bean 的其他组件在您的测试套件中测试 类,然后 ApplicationContext
将仅加载一次并 缓存 跨不同的集成测试 类.
此致,
Sam(Spring TestContext Framework 的作者)
我找到了一种自动模拟缺少的 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;
}
}
它也应该通过创建自己的 AbstractContextLoader
实现来注册,基于 GenericXmlWebContextLoader
。不幸的是,后者有一个 final
loadContext(MergedContextConfiguration mergedConfig)
方法,因此需要完全复制其实现(比如 class AutoMockGenericXmlWebContextLoader
),但有一个区别:
GenericWebApplicationContext context =
new GenericWebApplicationContext(new AutoMockBeanFactory());
不能在测试中使用:
@ContextConfiguration(
value = "classpath:context-test.xml",
loader = AutoMockGenericXmlWebContextLoader.class)
就像发布的 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;
}
}