복붙노트

[SPRING] 스프링 부트 유닛 테스트에서 컴포넌트 스캔이 작동하지 않는 이유는 무엇입니까?

SPRING

스프링 부트 유닛 테스트에서 컴포넌트 스캔이 작동하지 않는 이유는 무엇입니까?

FooServiceImpl 서비스 클래스에는 @Service라는 @Component라는 주석이 달려있어 autowiring에 적합합니다. 유닛 테스트 중에이 클래스가 선택되지 않고 자동 실행되지 않는 이유는 무엇입니까?

@Service
public class FooServiceImpl implements FooService {
    @Override
    public String reverse(String bar) {
        return new StringBuilder(bar).reverse().toString();
    }
}

@RunWith(SpringRunner.class)
//@SpringBootTest
public class FooServiceTest {
    @Autowired
    private FooService fooService;
    @Test
    public void reverseStringShouldReverseAnyString() {
        String reverse = fooService.reverse("hello");
        assertThat(reverse).isEqualTo("olleh");
    }
}

테스트가 applicationcontext를로드하지 못했습니다.

2018-02-08T10:58:42,385 INFO    Neither @ContextConfiguration nor @ContextHierarchy found for test class [io.github.thenilesh.service.impl.FooServiceTest], using DelegatingSmartContextLoader
2018-02-08T10:58:42,393 INFO    Could not detect default resource locations for test class [io.github.thenilesh.service.impl.FooServiceTest]: no resource found for suffixes {-context.xml}.
2018-02-08T10:58:42,394 INFO    Could not detect default configuration classes for test class [io.github.thenilesh.service.impl.FooServiceTest]: FooServiceTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2018-02-08T10:58:42,432 INFO    Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener, (...)org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
2018-02-08T10:58:42,448 INFO    Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@f0ea28, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@16efaab,(...)org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener@9604d9]
2018-02-08T10:58:42,521 INFO    Refreshing org.springframework.context.support.GenericApplicationContext@173f9fc: startup date [Thu Feb 08 10:58:42 IST 2018]; root of context hierarchy
2018-02-08T10:58:42,606 INFO    JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2018-02-08T10:58:42,666 ERROR    Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@19aaa5] to prepare test instance [io.github.thenilesh.service.impl.FooServiceTest@57f43]
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'io.github.thenilesh.service.impl.FooServiceTest': Unsatisfied dependency expressed through field 'fooService'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'io.github.thenilesh.service.FooService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    . . . 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192) [.cp/:?]
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'io.github.thenilesh.service.FooService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1493) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    ... 28 more
2018-02-08T10:58:42,698 INFO    Closing org.springframework.context.support.GenericApplicationContext@173f9fc: startup date [Thu Feb 08 10:58:42 IST 2018]; root of context hierarchy

전체 스택 추적

테스트 클래스에 @SpringBootTest 주석이 달린 경우 데이터베이스 연결과이 단위 테스트에 필요하지 않은 많은 비 관련 빈을 포함한 전체 응용 프로그램 컨텍스트를 만듭니다 (단위 테스트가 아닙니다!). 예상되는 것은 FooService가 의존하는 bean만이 @MockBean으로 조롱 된 것을 제외하고는 인스턴스화되어야한다는 것입니다.

해결법

  1. ==============================

    1.@SpringBootTest (classes = FooServiceImpl.class)를 사용해야합니다.

    @SpringBootTest (classes = FooServiceImpl.class)를 사용해야합니다.

    Annotation Type에서 언급했듯이 SpringBootTest :

    이것은 필요한 클래스 만로드합니다. 지정하지 않으면 데이터베이스 구성 및 테스트 속도를 저하시키는 다른 요소가로드 될 수 있습니다.

    반면에, 당신이 정말로 유닛 테스트를 원한다면 Spring없이이 코드를 테스트 할 수 있습니다. 그러면 @RunWith (SpringRunner.class)와 @SpringBootTest 주석은 필요 없습니다. FooServiceImpl 인스턴스를 테스트 할 수 있습니다. Autowired / injected 속성이나 서비스가 있다면 setter, constructor, Mockito 모의를 통해 설정합니다.

  2. ==============================

    2.단위 테스트는 구성 요소를 독립적으로 테스트해야합니다. 단위 테스트를 위해 Spring Test 컨텍스트 프레임 워크를 사용할 필요조차 없습니다. Mockito, JMock 또는 EasyMock과 같은 조롱 프레임 워크를 사용하여 구성 요소의 종속성을 격리하고 기대를 확인할 수 있습니다.

    단위 테스트는 구성 요소를 독립적으로 테스트해야합니다. 단위 테스트를 위해 Spring Test 컨텍스트 프레임 워크를 사용할 필요조차 없습니다. Mockito, JMock 또는 EasyMock과 같은 조롱 프레임 워크를 사용하여 구성 요소의 종속성을 격리하고 기대를 확인할 수 있습니다.

    진정한 통합 테스트를 원하면 테스트 클래스에서 @SpringBootTest 주석을 사용해야합니다. 클래스 속성을 지정하지 않으면 @SpringBootApplication 주석이있는 클래스가로드됩니다. 이로 인해 db 연결과 같은 프로덕션 구성 요소가로드됩니다.

    이를 제거하기 위해 별도의 테스트 구성 클래스를 정의합니다. 예를 들어 프로덕션 대신 내장 데이터베이스를 정의합니다

    @SpringBootTest(classes = TestConfiguration.class)
    public class ServiceFooTest{
    }
    
    @Configuration
    @Import(SomeProductionConfiguration.class)
    public class TestConfiguration{
       //test specific components
    }
    
  3. ==============================

    3.약간의 차이점을 가지고 유사한 문제를 해결해야했습니다. 그것의 세부 사항을 공유하는 생각, 유사한 문제에 타격을 가한 사람들에게 선택권을 줄지도 모른다는 생각.

    약간의 차이점을 가지고 유사한 문제를 해결해야했습니다. 그것의 세부 사항을 공유하는 생각, 유사한 문제에 타격을 가한 사람들에게 선택권을 줄지도 모른다는 생각.

    필자는 모든 종속성 대신로드 된 필수 종속성만으로 통합 테스트를 작성하려고했습니다. 그래서 @SpringBootTest 대신 @DataJpaTest를 사용하기로했습니다. 그리고 @Service 빈을 구문 분석하기 위해 @ComponentScan도 포함해야했습니다. 그러나 ServiceOne이 다른 패키지의 매퍼 빈을 사용하기 시작한 순간, @ComponentScan으로로드 할 특정 패키지를 지정해야했습니다. 놀랍게도이 맵퍼를 자동 연결하지 않는 두 번째 서비스에 대해서도이 작업을 수행해야했습니다. 나는 그것이 실제로는 그렇지 않을 때이 서비스가 매퍼에 의존한다는 것을 독자에게 인상을 남기기 때문에 그 점을 좋아하지 않았다. 따라서 서비스의 패키지 구조가 종속성을보다 정확하게 나타 내기 위해 세부 조정되어야한다는 것을 알게되었습니다.

    요약하면 @SpringBootTest 대신 @ DataJpaTest + @ ComponentScan과 패키지 이름을 조합하여 레이어 별 종속성 만로드 할 수 있습니다. 이것은 우리의 의존성을보다 정확하게 나타낼 수 있도록 디자인을 미세 조정하는 데 도움이 될 수도 있습니다.

    1. com.java.service.ServiceOneImpl

    @Service
    public class ServiceOneImpl implements ServiceOne {   
      @Autowired
      private RepositoryOne repositoryOne;    
      @Autowired
      private ServiceTwo serviceTwo;      
      @Autowired
      private MapperOne mapperOne;
    }
    

    2. com.java.service.ServiceTwoImpl

    @Service
    public class ServiceTwoImpl implements ServiceTwo {   
      @Autowired
      private RepositoryTwo repositoryTwo;    
    }
    

    3. ServiceOneIntegrationTest

    @RunWith(SpringRunner.class)
    @DataJpaTest
    @ComponentScan({"com.java.service","com.java.mapper"})
    public class ServiceOneIntegrationTest {
    

    4. ServiceTwoIntegrationTest.java

    @RunWith(SpringRunner.class)
    @DataJpaTest
    @ComponentScan({"com.java.service","com.java.mapper"})
    public class ServiceTwoIntegrationTest {
    

    1. com.java.service.one.ServiceOneImpl

    @Service
    public class ServiceOneImpl implements ServiceOne {   
      @Autowired
      private RepositoryOne repositoryOne;    
      @Autowired
      private ServiceTwo serviceTwo;      
      @Autowired
      private MapperOne mapperOne;
    }
    

    2. com.java.service.two.ServiceTwoImpl

    @Service
    public class ServiceTwoImpl implements ServiceTwo {   
      @Autowired
      private RepositoryTwo repositoryTwo;    
    }
    

    3. ServiceOneIntegrationTest

    @RunWith(SpringRunner.class)
    @DataJpaTest
    @ComponentScan({"com.java.service","com.java.mapper"})
    public class ServiceOneIntegrationTest {
    

    4. ServiceTwoIntegrationTest.java

    @RunWith(SpringRunner.class)
    @DataJpaTest
    @ComponentScan({"com.java.service.two"}) // CHANGE in the packages
    public class ServiceTwoIntegrationTest {
    
  4. from https://stackoverflow.com/questions/48679637/why-component-scanning-does-not-work-for-spring-boot-unit-tests by cc-by-sa and MIT license