티스토리 뷰

회사 동료 분이 스프링 테스트를 하며 오류가 난다고 도움을 요청하셨는데, 살펴보니 크게 두 가지 이슈가 있었고 주요 문제는 @SpringBootApplication 클래스를 포함하지 않는 프로젝트라 @SpringBootTest가 수행이 되지 상황이었다.

 

이와 관련해서 자료를 찾다보니 마음에 드는 답변은 스프링 테스트 전반에 걸친 내용을 작성한 글 중 일부로 있었고, 간단한 해결 방법들은 지금과 같은 상황에서 좋은 해결책은 아니었다.

 

그래서 샘플 코드와 함께 내 나름대로의 해결 방식을 정리를 해두려고 한다.

 

올해 처음으로 회사에서 테스트 코드로 고민하는 분을 봐 반가웠다

 

 

1. 프로젝트 준비

에러 재현을 위해 구성한 프로젝트는 다음과 같다. 특이 사항이라면, 어플리케이션을 기동하기 위한 엔트리 포인트가 없다는 것이고 다음으로 SampleService가 의존하고 있는 SampleRepository 구현체가 해당 프로젝트에는 없다는 점이다.

 

 

 

어플리케이션 아키텍처는 아래 그림과 같이 DIP를 적용해, 어플리케이션 레이어의 서비스가 레포지토리 빈을(저수준 모듈) 직접 의존하는 것이 아니라 도메인 레이어의 인터페이스를(고수준 모듈) 의존하도록 구성하였다.

 

 

 

 

2. 테스트 에러 재현

테스트 코드를 아래와 같이 작성했을 때 문제가 되는 부분은 먼저 서비스에서 의존하고 있는 리포지토리 객체가 없다는 점이고, 다음으로는 @SpringBootApplication 어노테이션을 사용하는 클래스가 없으므로 @SpringBootTest가 사용하는 @SpringBootConfiguration을 찾을 수 없다는 점이다.

 

@SpringBootTest
class BootApplicationTests {
    @Autowired
    SampleService sampleService;

    @Test
    void di_test() {
        assertThat(sampleService.findContentsById(1L))
                .isEqualTo("hello, 1");
    }
}

 

에러 메세지

Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test
java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test

 

 

3. 해결

@SpringBootConfiguration을 찾을 수 없어서 발생하는 문제는 @SpringBootApplication 어노테이션을 사용하는 클래스가 있을 경우 발생하지 않게 할 수 있고, 실제로 몇몇 블로그나 포스팅에서는 @SpringBootApplication을 생성해서 사용하라는 답변도 있었다.

 

참고: @SpringBootApplication

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration 🔥
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    ...
}

 

하지만, 다른 어플리케이션의 의존성으로 사용하기 위해 만든 프로젝트에서 어플리케이션의 엔트리포인트가 되는 어노테이션을 사용할 필요가 없으므로 다른 방식으로 해결하려고 한다.

 

 

 

테스트 코드

에러 메세지에 친절히 나와있는 것처럼 @SpringBootTest 어노테이션 내에 classes 속성을 활용해, @Configuration 클래스를 지정한다. 참고로, classes 속성은 지정한 클래스를 빈으로 생성한다.

@SpringBootTest(classes = SampleRepositoryConfiguration.class)
class BootApplicationTests {
    @Autowired
    SampleService sampleService;

    @Test
    void di_test() {
        assertThat(sampleService.findContentsById(1L))
                .isEqualTo("hello, 1");
    }
}

 

 

configuration

@SpringBootTest의 classes 속성으로 지정한 클래스에서는 서비스에서 의존하고 있는 리포지토리의 구현체를 만들고, @ComponentScan 어노테이션을 활용해 테스트에서 필요한 빈들을 생성하도록 한다.

@Configuration
@ComponentScan(basePackages = "com.example")
public class SampleRepositoryConfiguration {
    @Bean
    public SampleRepository sampleRepository() {
        return id -> "hello, " + id;
    }
}

 

 

테스트 결과

테스트를 위한 리포지토리 구현체를 만들어 서비스 빈을 정상적으로 생성하여, 테스트가 통과했다는 것을 확인할 수 있다.

 

 

 


샘플 코드 🤓

 

댓글