mock bean, 실제 bean을 하나의 ApplicationContext에서 실행하기

S-J LEE·2025년 3월 21일

TEST

목록 보기
2/2

서론

여러 글에서 말한바와 같이, springboot의 테스트 속도를 느리게 만드는 주요 원인은 ApplicationContext이 계속해서 로딩 되는 경우이다.

ApplicationContext의 caching을 적극 활용하여 테스트 속도를 빠르게 하는것이 무엇보다도 최우선 순위라고 볼 수 있다.

물론 ApplicationContext를 띄우지 않고 모든 테스트를 진행하는것이 좋긴 하지만, 테스트를 작성하는것이 어렵고 귀찮아진다. 하지만 이론상으로, 하나의 ApplicationContext로 테스트를 진행할 수 있으면, 정말 빠르게 테스트를 수행 할 수 있다.

특히 테스트 작성시 @MockBean 등을 사용하는 경우는, ApplicationContext를 반드시 생성시키므로, 삼가해야 할 패턴이다.

그렇다면 mocking해야할 bean이 있을 때 어떻게 해야 할까?


본론

예시 코드를 보여주겠다.

  • controller
@RestController
class ExampleController(
    private val exampleService: ExampleService,
) {
    @RequestMapping("/sample1")
    @GetMapping
    fun sample(): String {
        return exampleService.execute()
    }
}
  • service
@Service("exampleService")
class ExampleService {
    fun execute(): String {
        val result = "this is real ExampleService"
        return result
    }
}

이런 매우 간단한 contoller & service가 있다. 테스트를 작성해보겠다.

@SpringBootTest
class ExampleControllerTest(
    @Autowired
    private val exampleService: ExampleService
) {
    @Test
    fun test1() {
        val result =exampleService.execute()
        println(result)
    }
}

이때, exampleService를 mocking하고 싶다면, mocking한 bean을 더 만들어 주면 된다.

@TestConfiguration
class ExampleConfiguration {

    @Autowired
    private lateinit var exampleService: ExampleService

    @Bean("mockExampleService")
    fun mockExampleService(): ExampleService {
        val mockService: ExampleService = mockk(relaxed = true)
        val mockResult = "This is mock result"
        every { mockService.execute() } returns mockResult
        return mockService
    }
}

여기서 의문이 생길 수도 있다. exampleService를 테스트에서 당장 호출 하고 있으니, 문제가 없는 것 아닌가 하고 말이다.

그렇다면, wrapping class로 테스트를 해보면 된다.

기존 service는 유지하 되, 새로운 컨트롤러와 컴포넌트를 만들어 보자.

@RestController
class ExampleController2(
    private val exampleComponent: ExampleComponent,
){
    @RequestMapping("/sample2")
    @GetMapping
    fun sample2(): String {
        return exampleComponent.componentExecute()
    }
}

@Component
class ExampleComponent(
    private val exampleService: ExampleService
) {
    fun componentExecute(): String {
        return exampleService.execute()
    }
}

의문점을 테스트 해 볼 좋은 구조가 완성 되었다.
Controller단을 테스트 할 때, service만을 mocking했을 때, @MockBean처럼 ExampleComponent가 사용하는 service가 mocking 되는 지를 테스트 해볼 수 있게 되었다.

테스트는 다음과 같다.

@ContextConfiguration(classes = [ExampleConfiguration::class])
@SpringBootTest
class ExampleControllerWrappingTest(
    @Autowired
    private val mockExampleService: ExampleService
) {
    @Test
    fun test1() {
        val result = mockExampleService.execute()
        println(result)
    }
}

과연 결과는!!

역시나 동작을 잘한다. 왠만해서는 mockbean 대신 귀찮더라도 이러한 형태로 bean을 주입해서 사용하길 바란다.

마지막으로, 정말로 ApplicationContext가 한번만 생성되는지 테스트 3개를 돌려보겠다.

간단하게 applicaitonContext가 한번만 호출 되는 것을 확인 할 수 있다.

profile
MSA 와 관련된 기반 기술에 관심이 많습니다.

0개의 댓글