如何在SpringJUnit4ClassRunner中模拟缺少的bean定义?

时间:2018-03-06 06:47:30

标签: java spring junit spring-test spring-4

我有一个Spring 4 JUnit测试,它应该只验证我的应用程序的特定部分。

@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:context-test.xml")
@ActiveProfiles("test")
public class FooControllerIntegrationTest {
     ...
}

所以我不想配置和实例化所有那些实际上没有涉及我的测试范围的bean。例如,我不想配置另一个我不打算在这里测试的控制器中使用的bean。

但是,因为我不想缩小组件扫描路径,我得到“没有类型的限定bean”例外:

  

引起:   org.springframework.beans.factory.NoSuchBeanDefinitionException:没有   属于[...

的限定bean

如果我确定他们没有参与我正在测试的功能,有什么方法可以忽略这些错过的定义?

4 个答案:

答案 0 :(得分:1)

  

如果我确定他们没有参与我正在测试的功能,有什么方法可以忽略这些错过的定义?

不,没有用于此目的的自动或内置机制。

如果您指示Spring加载对其他bean具有强制依赖性的bean,那么其他bean必须存在。

出于测试目的,限制bean活动范围的最佳实践包括配置的模块化(例如,水平切片允许您有选择地选择加载应用程序的哪些层)以及使用bean定义配置文件

如果你正在使用Spring Boot,那么你也可以在Spring Boot Test中使用“测试切片”或@MockBean / @SpyBean

但是,您应该记住,在给定的集成测试中加载您没有使用的bean通常并不坏,因为您(希望)测试其他组件实际上需要其他测试类中的那些bean在您的测试套件中,ApplicationContext只会在不同的集成测试类中加载一次并缓存

此致

Sam( Spring TestContext Framework的作者

答案 1 :(得分:0)

如果你不缩小你的组件扫描范围,那么通常你会有所有可用于测试的bean,除了一些有条件可用的特定bean(例如spring-batch定义的bean)

在这种情况下,一个对我有用的选项是将这些依赖关系和组件标记为@Lazy。这将确保只在需要时才加载它们。请注意(取决于方案)您可能必须将@Autowired依赖项和@Component标记为@Lazy

答案 2 :(得分:0)

我找到了一种如何自动模拟缺少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)方法,所以需要完全复制它的实现(比如在类AutoMockGenericXmlWebContextLoader中),但有一点不同:

GenericWebApplicationContext context = new GenericWebApplicationContext(new AutoMockBeanFactory());

不能在测试中使用它:

@ContextConfiguration(
     value = "classpath:context-test.xml", 
     loader = AutoMockGenericXmlWebContextLoader.class)

答案 3 :(得分:0)

就像发布的OP一样,这是等效于注入任何缺少的模拟豆的注释上下文:

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;
    }
}