본문 바로가기

Troubleshooting

테스트의 Spring Container와 운영 환경의 Spring Container가 다르다..?

들어가면서

이번 글에서는 개발하면서 만난 이슈를 해결해 가는 과정을 공유하겠다. 

 

Issue

서버 상태를 확인해주는 HealthCheck API를 만들었다. 이 때 ServletWebServerApplicationContext를 이용해 서버 포트 번호를 확인했다.

@RestController
@RequestMapping("/health")
class HealthCheckController(
    private val webServerAppCtxt: ServletWebServerApplicationContext,
) {

    @GetMapping("/info")
    fun serverInfo(request: HttpServletRequest) = mapOf(
        "IPAdress" to request.getHeader("X-FORWARDED-FOR"),
        "Port" to webServerAppCtxt.webServer.port.toString()
    )
}

 

개발을 끝내고 테스트를 작성했다.

@SpringBootTest
@AutoConfigureMockMvc
internal class HealthCheckControllerTest(
    private val mockMvc: MockMvc,
) : FunSpec({
    test("get /health/info should return server info") {
        mockMvc.get("/health/info").andExpect {
            status { isOk() }
            jsonPath("\$.port") { value(8080) }
        }.andReturn()
    }
})

 

하지만 예상과 달리 테스트가 실패했다. 에러 로그는 아래와 같았다.

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

로그 내용은 ServletWebServerApplicationContext 타입을 가진 빈을 찾을 수 없다는 것이었다. 하지만!! 로컬에서 실행하면 정상적으로 실행됐다.

 

Why?

문제의 원인은 Spring Container의 구현체가 테스트 환경과 로컬이 다르기 때문이라고 예상됐다. 가설을 검증하기 위해 디버거를 이용해 각 환경에서 Spring Cotainer 구현체가 무엇인지 확인했다.

 

로컬 환경에서 Spring Context

ServletWebServerApplicationContextFactory에 의해 AnnotationConfigServletWebServerApplicationContext 구현체가 사용된다.

ServletWebServerApplicationContextFactory

 

AnnotationConfigServletWebServerApplicationContext의 Hierarchy는 다음과 같다.

AnnotationConfigServletWebServerApplicationContext

AnnotationConfigServletWebServerApplicationContext는 ServletWebServerApplicationContext를 상속했다.

 

 

테스트 환경에서 Spring Context

SpringBootContextLoader에 의해 GenericWebApplicationContext 구현체가 사용된다.

SpringBootContextLoader

 

GenericWebApplicationContext의 Hierarchy는 다음과 같다.

GenericWebApplicationContext

 

나의 가설이 맞았다! 테스트 환경에서는 Spring Container의 구현체로 GenricWebApplicationContext가 사용돼 ServletWebServerApplicationContext를 찾을 수 없어 테스트가 실패했던 것이였다. 

 

차이가 발생한 이유는 SpringBootContextLoader에 있었다.

SpringBootContextLoader

테스트 환경에서 GenericWebApplicationContext를 구현체로 사용한 이유는, 서버가 EmbeddedWebEnvironment를 사용하지 않고, MockWebEnvironment를 사용했기 때문이다. 

 

@SpringBootTest를 사용하면 기본값이 MockWebEnvironment이다.

The type of web environment to create when applicable. Defaults to SpringBootTest.WebEnvironment.MOCK.

 

해결

테스트 환경에서 EmbeddedWebEnvironment를 사용하도록 수정했다. (WebEnvironment.RANDOM_PORT와 WebEnvironment.DEFINED_PORT는 EmbeddedWebEnvironment를 사용한다.)

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@AutoConfigureMockMvc
internal class HealthCheckControllerTest(
    private val mockMvc: MockMvc,
) : FunSpec({
    ...
})

 

그 결과 테스트에 성공했다.

 

정리

이번 글을 통해 내가 만났던 이슈와 해결해 가는 과정을 공유했다. 문제 해결 과정에서 WebEnvironment가 Spring Container 구현체 선택에 영향을 준다는 사실을 배울 수 있었다.