如何在@ Configuration / @ Bean使用的单元测试中禁用Spring自动装配

时间:2014-09-24 11:57:41

标签: java spring junit mockito spring-test

我想使用spring-test配置内部类(@Configuration)配置组件测试。经测试的组件有一些我想要测试的服务。这些服务是类(没有使用接口)并且在其中具有spring注释(@Autowired)。 Mockito很容易嘲笑它们,然而,我发现没有办法禁用弹簧自动装配。

示例我如何轻松复制:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SomeTest.Beans.class)
public class SomeTest {

    // configured in component-config.xml, using ThirdPartyService
    @Autowired
    private TestedBean entryPoint;

    @Test
    public void test() {
    }

    @Configuration
    @ImportResource("/spring/component-config.xml")
    static class Beans {
        @Bean
        ThirdPartyService createThirdPartyService() {
            return mock(ThirdPartyService.class);
        }
    }
}

public class ThirdPartyService {
    @Autowired
    Foo bar;
}

public class TestedBean {
    @Autowired
    private ThirdPartyService service;
}

在这个例子中" TestBean"代表要嘲笑的服务。我不喜欢" bar"被春天注入! @Bean(autowire = NO)无效(实际上,这是默认值)。 (请保存我从#34;使用界面!"评论 - 模拟服务可以是第三方,我无法做任何事情。)

更新

Springockito可以部分解决问题,只要您不必配置任何其他东西(因此您无法使用Springockito配置类 - 它不支持它),但仅使用模拟。 还在寻找纯粹的弹簧解决方案,如果有的话......

6 个答案:

答案 0 :(得分:13)

以下是我解决问题的方法:

import static org.mockito.Mockito.mockingDetails;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MockitoSkipAutowireConfiguration {

@Bean MockBeanFactory mockBeanFactory() {
    return new MockBeanFactory();
}

private static class MockBeanFactory extends InstantiationAwareBeanPostProcessorAdapter {
    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        return !mockingDetails(bean).isMock();
    }
}

} 

然后只是

@Import(MockitoSkipAutowireConfiguration.class)

在你的考试@Configuration中,你已经完成了

答案 1 :(得分:5)

我通过为我的bean创建FactoryBean而不是仅仅模拟bean来解决它。通过这种方式,Spring不会尝试自动装载领域。

工厂bean帮助班:

public class MockitoFactoryBean<T> implements FactoryBean<T> {
    private final Class<T> clazz;

    public MockitoFactoryBean(Class<T> clazz) {
        this.clazz = clazz;
    }

    @Override public T getObject() throws Exception {
        return mock(clazz);
    }

    @Override public Class<T> getObjectType() {
        return clazz;
    }

    @Override public boolean isSingleton() {
        return true;
    }
}

实际测试上下文部分:

@Configuration
public class TestContext {

    @Bean
    public FactoryBean<MockingService> mockingService() {
        return new MockitoFactoryBean<>(MockingService.class);
    }
}

答案 2 :(得分:2)

检查Spring profiles。您不需要禁用自动布线,您需要为不同的配置注入不同的bean。

答案 3 :(得分:2)

您可以通过org.springframework.beans.factory.config.SingletonBeanRegistry #registerSingleton手动将模拟服务添加到spring应用程序上下文中。这样,模拟不会被弹簧进行后处理,弹簧也不会尝试自动装配模拟。模拟本身将被注入到测试的bean中。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SomeTest.Beans.class)
public class SomeTest {

    // configured in component-config.xml, using ThirdPartyService
    @Autowired
    private TestedBean entryPoint;

    @Autowired
    private ThirdPartyService thirdPartyServiceMock;

    @Test
    public void test() {
    }

    @Configuration
    static class Beans {

        @Autowired
        private GenericApplicationContext ctx;

        @Bean
        TestedBean testedBean() {
            ctx.getBeanFactory().registerSingleton("thirdPartyService", mock(ThirdPartyService.class));
            return new TestedBean();
        }
    }

    public static class ThirdPartyService {
        @Autowired
        Object bar;
    }

    public static class TestedBean {
        @Autowired
        private ThirdPartyService service;

    }
}

答案 4 :(得分:1)

我处于相同的情况。

我发现如果你没有在测试类上通过@ContextConfiguration批注设置上下文加载器,将使用默认的上下文加载器,它派生自AbstractGenericContextLoader。我查看了它的源代码,结果发现它注册了所有负责读取@Autowired注释的bean后处理器。换句话说,默认情况下启用注释配置。

所以主要的问题是有两种配置存在冲突:在java配置中我们说不需要自动装配,而自动装配的注释则反之亦然。真正的问题是如何禁用注释处理以消除不需要的配置。

据我所知,没有这样的SpringLoader的Spring实现,它不会从AbstractGenericContextLoader派生出来,所以我想我们唯一能做的就是编写自己的。它会是这样的:

public static class SimpleContextLoader implements ContextLoader {

    @Override
    public String[] processLocations(Class<?> type, String... locations) {
        return strings;
    }

    @Override
    public ApplicationContext loadContext(String... locations) throws Exception {
        // in case of xml configuration
        return new ClassPathXmlApplicationContext(strings);
        // in case of java configuration (but its name is quite misleading)
        // return new AnnotationConfigApplicationContext(TestConfig.class);
    }

}

当然,花更多时间来了解如何正确实现ContextLoader是值得的。

干杯,
罗伯特

答案 5 :(得分:0)

有很多方法可以做到这一点,我很确定这个答案是不完整的,但这里有几个选项......

由于目前似乎是推荐的做法,因此请为您的服务使用构造函数注入,而不是直接自动装配字段。这使得像这样的测试变得更加容易。

public class SomeTest {

    @Mock
    private ThirdPartyService mockedBean;

    @Before
    public void init() {
        initMocks(this);
    }

    @Test
    public void test() {
        BeanUnderTest bean = new BeanUnderTest(mockedBean);
        // ...
    }

}

public class BeanUnderTest{
    private ThirdPartyService service;
    @Autowired
    public BeanUnderTest(ThirdPartyService ThirdPartyService) {
        this.thirdPartyService = thirdPartyService;
    }
}

通过这样做,您还可以通过自动装配到测试本身混合自动装配和模拟服务,然后使用最有用的自动装配和模拟bean组合构建测试中的bean。

合理的替代方法是使用Spring配置文件来定义存根服务。当希望在多个测试中使用相同的存根特征时,这尤其有用:

@Service
@Primary
@Profile("test")
public class MyServiceStub implements MyService {
    // ...
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SomeTest.Beans.class) 
@ActiveProfiles({"test"})
public class SomeTest {
    // ...
}

通过使用@Primary注释,它确保将使用此存根bean而不是实现MyService接口的任何其他bean。我倾向于将这种方法用于电子邮件服务,通过更改配置文件,我可以在真实的邮件服务器和Wiser之间切换。