java 如何在@Configuration/@Bean 使用的单元测试中禁用 Spring 自动装配
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/26016311/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to disable Spring autowiring in unit tests for @Configuration/@Bean usage
提问by vuk
I want configure a component test using spring-test configuration inner class (@Configuration
). Tested components has some services which I'd like to mock for the test. These services are classes (no interface used) and have spring annotations (@Autowired
) in them. Mockito can easily mock them, however, I found no way of disabling spring autowiring.
我想使用 spring-test 配置内部类 ( @Configuration
)配置组件测试。经过测试的组件有一些我想为测试模拟的服务。这些服务是类(未使用接口)并且其中包含弹簧注释 ( @Autowired
)。Mockito 可以轻松模拟它们,但是,我发现无法禁用弹簧自动装配。
Example how I can easily reproduce:
示例我如何轻松重现:
@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;
}
In this example "TestBean" represents the service to be mocked. I would NOT like "bar" to be injected by spring! @Bean(autowire = NO)
does not help (in fact, that's the default value).
(Please save me from "use interfaces!" comments - the mocked service can be 3rd party which I can't do anything with.)
在此示例中,“TestBean”表示要模拟的服务。我不希望春天注入“酒吧”!@Bean(autowire = NO)
没有帮助(实际上,这是默认值)。(请让我远离“使用接口!”评论 - 模拟服务可以是我无法做任何事情的第三方。)
UPDATE
更新
Springockito partially solves the problem, as long as you don't have to have anything else to configure (so you can't use configuration class with Springockito - it does not support it), but use mocks only. Still looking for pure spring solution, if there's any...
Springockito 部分解决了这个问题,只要你不需要配置任何其他东西(所以你不能在 Springockito 中使用配置类——它不支持它),而只使用模拟。仍在寻找纯弹簧解决方案,如果有的话......
回答by Pawe? Kaczorowski
Here is my solution to your problem:
这是我对您的问题的解决方案:
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();
}
}
}
and then just
然后只是
@Import(MockitoSkipAutowireConfiguration.class)
in your test @Configuration
and you are all set
在你的测试中@Configuration
,你已经准备好了
回答by tr1cks
I solved it by creating FactoryBean for my bean instead of just mocking bean. At this way Spring don't try to autowire fields.
我通过为我的 bean 创建 FactoryBean 而不是仅仅模拟 bean 来解决它。这样 Spring 就不会尝试自动装配字段。
Factory bean helping class:
工厂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;
}
}
Actual test context part:
实际测试上下文部分:
@Configuration
public class TestContext {
@Bean
public FactoryBean<MockingService> mockingService() {
return new MockitoFactoryBean<>(MockingService.class);
}
}
回答by Adisesha
Check Spring profiles. You don't need to disable auto wiring, you need to inject different beans for different configuration.
检查Spring 配置文件。您不需要禁用自动连接,您需要为不同的配置注入不同的 bean。
回答by Oliver
You could add the mocked service manually to the spring application context via org.springframework.beans.factory.config.SingletonBeanRegistry#registerSingleton. This way the mock is not post-processed by spring and spring does not attempt to autowire the mock. The mock itself will be injected into your tested bean.
您可以通过 org.springframework.beans.factory.config.SingletonBeanRegistry#registerSingleton 手动将模拟服务添加到 spring 应用程序上下文。这样模拟就不会被 spring 后处理,并且 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;
}
}
回答by Robert
I am in quite the same situation.
我处于完全相同的情况。
What I found that if you do not set the context loader by @ContextConfiguration annotation on your test class, the default context loader will be used, which derived from AbstractGenericContextLoader. I had a look at its source and turned out it registers all the bean post processors which are responsible for reading annotations such @Autowired. In other words, annotation config is enabled by default.
我发现如果你没有在测试类上通过 @ContextConfiguration 注释设置上下文加载器,将使用默认的上下文加载器,它派生自 AbstractGenericContextLoader。我查看了它的源代码,结果发现它注册了所有负责读取 @Autowired 等注释的 bean 后处理器。换句话说,默认情况下启用注释配置。
So the main problem is that there are two configurations which are in conflict: in the java config we said that autowiring is not needed, while the autowired annotation tells the opposite. The real question is how to disable the annotation processing in order to eliminate the undesired configuration.
所以主要问题是有两个配置冲突:在java配置中我们说不需要自动装配,而自动装配注释则相反。真正的问题是如何禁用注释处理以消除不需要的配置。
As far as I know there is no such spring implementation of ContextLoader which would not be derived from AbstractGenericContextLoader so I guess the only we can do is to write our own. It would be something like this:
据我所知,没有这样的 ContextLoader 实现不是从 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);
}
}
Of course it would be worth to spend more time to find out how to implement ContextLoader properly.
当然,花更多的时间来了解如何正确实现 ContextLoader 是值得的。
Cheers,
Robert
干杯,
罗伯特
回答by Steve
There are so many ways of doing this, I'm pretty sure that this answer will be incomplete, but here are a few options...
有很多方法可以做到这一点,我很确定这个答案是不完整的,但这里有一些选择......
As currentlyseems to be recommended practice, use constructor injection for your services rather than autowiring the fields directly. This makes testing like this somuch easier.
由于目前似乎是推荐的做法,使用构造函数注入为你服务,而不是直接自动装配等领域。这使得测试像这样这么容易得多。
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;
}
}
By doing that, you can also mix up autowired and mocked services by autowiring into the test itself and then constructing the beans under test with the most useful mix of autowired and mocked beans.
通过这样做,您还可以通过自动装配到测试本身,然后使用最有用的自动装配和模拟 bean 的组合构建被测 bean 来混合自动装配和模拟服务。
A reasonable alternative is to use Spring profiles to define stub services. This is particularly useful when wish to use the same stubbed features in multiple tests:
一个合理的替代方法是使用 Spring 配置文件来定义存根服务。当希望在多个测试中使用相同的存根功能时,这特别有用:
@Service
@Primary
@Profile("test")
public class MyServiceStub implements MyService {
// ...
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SomeTest.Beans.class)
@ActiveProfiles({"test"})
public class SomeTest {
// ...
}
By using the @Primary
annotation, it ensures that this stub bean will be used instead of any other bean implementing the MyService
interface. I tend to use this approach for things like email services, where by changing profile, I'm able to switch between a real mail server and Wiser.
通过使用@Primary
注释,它确保将使用这个存根 bean 而不是实现MyService
接口的任何其他 bean 。我倾向于将这种方法用于电子邮件服务之类的事情,通过更改配置文件,我可以在真正的邮件服务器和 Wiser 之间切换。