【问题标题】:How to disable Spring autowiring in unit tests for @Configuration/@Bean usage如何在 @Configuration/@Bean 使用的单元测试中禁用 Spring 自动装配
【发布时间】:2014-11-18 21:53:14
【问题描述】:

我想使用 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”代表要模拟的服务。我不希望在春天注入“酒吧”! @Bean(autowire = NO) 没有帮助(事实上,这是默认值)。 (请把我从“使用接口!”cmets 中拯救出来——模拟的服务可以是我无法做任何事情的第 3 方。)

更新

Springockito 部分解决了这个问题,只要您不需要配置其他任何东西(因此您不能将配置类与 Springockito 一起使用 - 它不支持它),但只能使用模拟。 还在寻找纯弹簧解决方案,如果有的话……

【问题讨论】:

  • 您能否详细说明您要测试的内容?最佳解决方案很大程度上取决于您实际想要测试的内容
  • 我不确定你是否必须模拟其他模拟的依赖关系。至少,它打破了我的单元测试世界。
  • @geo 我稍微更新了这个例子。我想测试在主范围内配置的 bean,但必须在测试范围内部分模拟上下文才能使其工作。问题是模拟类具有 Autowired 注释,并且无法关闭注入它。
  • @walv 这就是重点..我不想依赖模拟,但春天有点强迫我。
  • @vuk 这是见仁见智的问题。但是我个人认为在字段上使用@Autowired 绝不是一个好主意。它会像您指定的情况一样杀死重用。所以在这种情况下,这不是测试适应的问题,而是使组件易于重用的问题,无论上下文如何。解决您的问题的另一种方法是注册一个自定义 AutowiredAnnotationBeanPostProcessor,它将忽略您想要的字段。您可以将其与 Spring 配置文件结合使用,以便仅将其用于测试。如果您想进一步探索,我可以将其添加为包含更多信息的答案

标签: java spring junit mockito spring-test


【解决方案1】:

这是我对您的问题的解决方案:

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 并且你已经准备好了

【讨论】:

  • 我无法测试这个,但谢谢!我会在测试后通知您。
  • 我遇到了同样的问题,而且效果很好,谢谢!这个答案应该被接受
  • 这应该是公认的答案,就像一个魅力。
  • 如果你有非常旧的 Mockito 版本
  • 这非常适用于一堆你需要一个类但又不想模拟出 20 个依赖项来满足无法测试/触及的东西的东西。谢谢!
【解决方案2】:

我通过为我的 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);
    }
}

【讨论】:

【解决方案3】:

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

【讨论】:

  • 我如何能够通过使用配置文件将模拟注入到上下文中(并且还避免弹簧自动装配到模拟中 - 因为这是这里的主要问题)?...你能举个例子,好吗?
  • +1 不错的答案。Here 一个简单的弹簧轮廓示例。
  • @Adi 据此,仅使用弹簧没有解决方案。是这样吗?..
  • 点击最后​​一个答案中的链接(指向重复项)。答案是 spring 和 Mockito 的混合。
【解决方案4】:

您可以通过 org.springframework.beans.factory.config.SingletonBeanRegistry#registerSingleton 手动将模拟服务添加到 Spring 应用程序上下文。这样,spring 不会对 mock 进行后处理,并且 spring 不会尝试自动装配 mock。模拟本身将被注入到您测试的 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;

    }
}

【讨论】:

  • 我在寻找更优雅的东西。另外,我期待我的测试类在 xml 中声明(包括代码的主要部分)。但是,我绝对可以在 \@Configuration 类或测试类的 \@PostConstruct 部分中注册。
【解决方案5】:

我的情况完全一样。

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

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

据我所知,没有这样的 ContextLoader 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。

干杯,
罗伯特

【讨论】:

    【解决方案6】:

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

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

    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 之间切换。

    【讨论】:

    • 在您的第一个示例中,没有 Spring 配置,与我的问题无关(如何将模拟注入上下文?)。如果上下文必须具有 ThirdPartyService 实例,则包装器无济于事。在第二个..我知道存根,但不确定这会有所帮助(我希望模拟为了测试而改变他们的行为)。此外,在此示例中,配置文件已过时,如果存根在测试范围内,则仅 @Primary 就可以解决问题(为什么不呢?)。
    • 你没有说你想要一个模拟注入到上下文中。对于该示例,您实际上并不需要 Spring 上下文。如果您希望 Spring 将模拟/存根自动装配到多个 bean 中,那么基于配置文件的方法会更好。如果您希望能够更改它在单个测试中返回的内容,请以这种方式编写它而不是让它返回固定值。并且仅使用 @Primary 并不是一种安全的方法。使用配置文件要好得多。
    • 我认为原来的例子很清楚。我在 spring 中配置了一个服务,并将一个模拟服务注入到配置的服务中。仍然没有人解释分析如何解决注入的模拟(尤其是原始问题:没有自动装配到它们中)。我不想要存根,我想要在测试中具有内联配置行为的模拟。
    • 我能想到的最好的方法是使用构造函数注入方法并注入一个模拟的ThirdPartyService。没有 Spring 创建 ThirdPartyService bean 并将模拟对象注入其中。即保持边界清洁并测试您的应用程序如何与该服务交互,而不是该服务是否有效。
    • 否则,正如我在回答中提到的,您可以在您的测试类中创建一个 @Autowired 字段并替换您的模拟中注入的 Foo bar。当然,这取决于该字段是否是私有的,以及第三方是否提供了 setter 或构造函数来注入它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-07-11
    • 2015-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-29
    相关资源
    最近更新 更多