簡體   English   中英

如何在@ Configuration / @ Bean使用的單元測試中禁用Spring自動裝配

[英]How to disable Spring autowiring in unit tests for @Configuration/@Bean usage

我想使用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)沒有幫助(實際上,這是默認值)。 (請保存我從“使用界面!”評論 - 模擬服務可以是第三方,我無法做任何事情。)

UPDATE

Springockito部分解決了這個問題,只要你沒有其他任何東西可以配置(所以你不能使用Springockito配置類 - 它不支持它),但只使用模擬。 還在尋找純彈簧解決方案,如果有的話...

以下是我的問題解決方案:

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 ,您已經完成了設置

我通過為我的bean創建FactoryBean而不是僅僅模擬bean來解決它。 這樣Spring就不會嘗試自動裝載字段。

工廠豆幫助班:

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

檢查Spring配置文件 您不需要禁用自動布線,您需要為不同的配置注入不同的bean。

您可以通過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;

    }
}

我處於相同的情況。

我發現如果你沒有在測試類上通過@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是值得的。

干杯,
羅伯特

有很多方法可以做到這一點,我很確定這個答案是不完整的,但這里有幾個選擇......

目前似乎是推薦的做法,為您的服務使用構造函數注入,而不是直接自動裝配字段。 這使得像這樣的測試輕松多了。

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之間切換。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM