2016-08-30 55 views
1

我已經決定寫一個相當簡單的測試來檢查我的Spring Boot自動配置是否工作 - 所有需要的bean都與它們的依賴關係一起創建。測試自定義Spring Boot AutoConfiguration是否有效

自動配置爲:

package org.project.module.autoconfigure; 

import org.project.module.SomeFactory; 
import org.project.module.SomeProducer; 
import org.project.module.SomeServiceClient; 
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.ComponentScan; 
import org.springframework.context.annotation.Configuration; 

/** 
* Spring Boot simple auto-configuration. 
* 
* @author istepanov 
*/ 
@Configuration 
@ComponentScan("org.project.module.support") 
public class SomeAutoConfiguration { 

    @Bean 
    @ConditionalOnMissingBean 
    public SomeFactory someFactory() { 
     return new SomeFactory(); 
    } 

    @Bean 
    @ConditionalOnMissingBean 
    public SomeServiceClient someServiceClient() { 
     return new SomeServiceClient(); 
    } 

    @Bean 
    @ConditionalOnMissingBean 
    public SomeProducer someProducer() { 
     return new SomeProducer(); 
    } 
} 

和測試是:

package org.project.module.autoconfigure; 

import org.project.module.SomeFactory; 
import org.project.module.SomeProducer; 
import org.project.module.SomeServiceClient; 
import org.junit.Test; 
import org.junit.runner.RunWith; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.boot.test.context.SpringBootTest; 
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 

import static org.assertj.core.api.Assertions.assertThat; 

/** 
* Tests for {@code SomeAutoConfiguration}. 
* 
* @author istepanov 
*/ 
@RunWith(SpringJUnit4ClassRunner.class) 
@SpringBootTest(classes = {SomeAutoConfiguration.class}, webEnvironment = SpringBootTest.WebEnvironment.NONE) 
public class SomeAutoConfigurationTest { 

    @Autowired 
    private SomeFactory someFactory; 
    @Autowired 
    private SomeServiceClient someServiceClient; 
    @Autowired 
    private SomeProducer someProducer; 

    @Test 
    public void someFactory_isNotNull() { 
     assertThat(someFactory).isNotNull(); 
    } 

    @Test 
    public void someServiceClient_isNotNull() { 
     assertThat(someServiceClient).isNotNull(); 
    } 

    @Test 
    public void someProducer_isNotNull() { 
     assertThat(someProducer).isNotNull(); 
    } 
} 

但實際上測試失敗,出現異常 - 依賴豆,預計將裝有@ComponentScan,實際上是缺少:

java.lang.IllegalStateException: Failed to load ApplicationContext 
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124) 
    at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83) 
    at org.springframework.boot.test.autoconfigure.AutoConfigureReportTestExecutionListener.prepareTestInstance(AutoConfigureReportTestExecutionListener.java:49) 
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230) 
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228) 
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287) 
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) 
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289) 
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247) 
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) 
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) 
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) 
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) 
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) 
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) 
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) 
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) 
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363) 
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) 
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137) 
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68) 
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'someFacade': Unsatisfied dependency expressed through method 'setSomeMetrics' parameter 0: Error creating bean with name 'someMetrics': Unsatisfied dependency expressed through method 'setCounterService' parameter 0: No qualifying bean of type [org.springframework.boot.actuate.metrics.CounterService] found for dependency [org.springframework.boot.actuate.metrics.CounterService]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.boot.actuate.metrics.CounterService] found for dependency [org.springframework.boot.actuate.metrics.CounterService]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'someMetrics': Unsatisfied dependency expressed through method 'setCounterService' parameter 0: No qualifying bean of type [org.springframework.boot.actuate.metrics.CounterService] found for dependency [org.springframework.boot.actuate.metrics.CounterService]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.boot.actuate.metrics.CounterService] found for dependency [org.springframework.boot.actuate.metrics.CounterService]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {} 
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:648) 
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88) 
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:349) 
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1214) 
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:543) 
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) 
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) 
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) 
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) 
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) 
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:776) 
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:861) 
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:541) 
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:759) 
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:369) 
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:313) 
    at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:111) 
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:98) 
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:116) 
    ... 22 more 
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'someMetrics': Unsatisfied dependency expressed through method 'setCounterService' parameter 0: No qualifying bean of type [org.springframework.boot.actuate.metrics.CounterService] found for dependency [org.springframework.boot.actuate.metrics.CounterService]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.boot.actuate.metrics.CounterService] found for dependency [org.springframework.boot.actuate.metrics.CounterService]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {} 
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:648) 
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88) 
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:349) 
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1214) 
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:543) 
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) 
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) 
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) 
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) 
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) 
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:207) 
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1214) 
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1054) 
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1019) 
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:640) 
    ... 40 more 
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.boot.actuate.metrics.CounterService] found for dependency [org.springframework.boot.actuate.metrics.CounterService]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {} 
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1406) 
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1057) 
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1019) 
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:640) 
    ... 54 more 

任何想法我錯過了什麼?

P.S:還添加缺少SomeMetrics:

package org.project.module.support.metrics; 

import org.project.module.support.SomeProperties; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.beans.factory.annotation.Value; 
import org.springframework.boot.actuate.metrics.CounterService; 
import org.springframework.boot.actuate.metrics.GaugeService; 
import org.springframework.stereotype.Component; 

import javax.annotation.PostConstruct; 

import static org.mockito.Mockito.mock; 

/** 
* Customization for Spring Actuator, defines application-specific counters and metrics. 
* 
* @author istepanov 
*/ 
@Component 
public class SomeMetrics { 

    @Value("${const.metrics.some.connections.current:some.connections.created}") 
    private String connectorsCurrent; 
    @Value("${const.metrics.some.connections.idle:some.connections.idle}") 
    private String connectorsIdle; 
    @Value("${const.metrics.some.connections.max:some.connections.max}") 
    private String connectorsMax; 

    private CounterService counterService; 
    private GaugeService gaugeService; 
    private SomeProperties someProperties; 

    @Autowired 
    public void setSomeProperties(SomeProperties someProperties) { 
     this.someProperties = someProperties; 
    } 

    @Autowired 
    public void setCounterService(CounterService counterService) { 
     this.counterService = counterService; 
    } 

    @Autowired 
    public void setGaugeService(GaugeService gaugeService) { 
     this.gaugeService = gaugeService; 
    } 

    /** 
    * Use mocks for {@link CounterService} and {@link GaugeService} if CRMBO is not configured properly. 
    */ 
    @PostConstruct 
    public void init() { 
     if (someProperties.isMock()) { 
      counterService = mock(CounterService.class); 
      gaugeService = mock(GaugeService.class); 
     } 
    } 

    public void decrementConnectorsCurrent() { 
     this.counterService.decrement(connectorsCurrent); 
    } 

    public void incrementConnectorsCurrent() { 
     this.counterService.increment(connectorsCurrent); 
    } 

    public void decrementConnectorsIdle() { 
     this.counterService.decrement(connectorsIdle); 
    } 

    public void incrementConnectorsIdle() { 
     this.counterService.increment(connectorsIdle); 
    } 

    public void decrementConnectorsMax() { 
     this.counterService.decrement(connectorsMax); 
    } 

    public void incrementConnectorsMax() { 
     this.counterService.increment(connectorsMax); 
    } 
} 
+0

如果刪除其中一個bean上的'@ ConditionalOnMissingBean'註釋會發生什麼? –

+0

沒有變化 - 同樣的例外。 – stepio

+0

據我所知,代碼'@ComponentScan(「org.project.module.support」)'被忽略,所以不創建低級別的bean。 – stepio

回答

1

這看起來更像是一個普通的Spring配置問題對我來說,並不一定是春天開機自動配置一個。

在您的測試中,您只配置了SomeAutoConfiguration來初始化您的應用程序上下文,並且感謝@ComponentScan註釋,它會發現要設置的其他組件,如SomeMetricsSomeMetrics依賴於某些Spring Boot Actuator bean的存在,這並不是由於測試中的上下文配置較窄所致。

你必須,如果你想要的東西的工作,或添加一些條件,你SomeMetrics組件,以防止它被創建,除非有必要的豆類,像這樣添加上下文豆類:

@Component 
@ConditionalOnBean({CounterService.class, GaugeService.class}) 
public class SomeMetrics { 

    // Content ommitted for brevity. 
} 

我不知道哪種解決方案更適合您的情況。

+0

感謝您的建議,@ThomasKåsene。我會嘗試創建@AndyWilkinson建議的所有bean,並且肯定會使用'@ ConditionalOnBean'進行遊戲。 – stepio

+0

但實際上我以爲'@ ConditionalOnBean'用於確定是否應該創建bean,而不是爲了排序。在實例化所有bean後執行注入,這不應該是問題。 – stepio

+0

它用於確定是否應該創建bean。我的意思是你的上下文甚至不包含任何'SomeMetrics'需要的bean,因爲你的測試沒有設置它們。雖然我同意你應該手動創建所有的bean來進行自動配置,但我不認爲這是這種情況下問題的根源。 –

2

爲什麼不從Spring Boot自帶的測試中獲得一些靈感來實現自動配置類?例如,JacksonAutoConfigurationTests

當您測試自動配置類時,通常需要在上下文中使用不同的bean和配置屬性進行測試,以便您可以驗證任何@ConditionalOnMissingBean@ConditionalOnProperty註釋的工作方式如預期。出於這個原因,測試不使用@SpringBootTest或Spring Framework的測試框架,它們需要爲類中的每個測試使用相同的應用程序上下文。

我也避免在自動配置類中使用@ComponentScan。 Spring Boot的自動配置都不使用它。相反,您的自動配置應通過@Bean方法或通過分別使用@Import@ImportResource導入其他基於Java和XML的配置來定義所有組件。

+0

謝謝,我剛剛嘗試過最簡單的方法,它的工作:)有罪。但是我會擺脫'@ ComponentScan'--最可能的是解決問題。 – stepio

+0

對Spring Boot提出了一些小改進:https://github.com/spring-projects/spring-boot/issues/6794 – stepio