context caching 이란?
테스트 시 구성되는 ApplicationContext 는 아래의 구성정보를 바탕으로 캐시 키를 발급한다.
해당 캐시 키를 바탕으로 application context 를 구성하거나 재사용하게 되어서, 테스트시 이러한 구성정보가 다르다면 새로운 컨텍스트가 생성된다.
- locations (from @ContextConfiguration)
- classes (from @ContextConfiguration)
- contextInitializerClasses (from @ContextConfiguration)
- contextCustomizers (from ContextCustomizerFactory) – this includes @DynamicPropertySource methods as well as various features from Spring Boot’s testing support such as @MockBean and @SpyBean.
- contextLoader (from @ContextConfiguration)
- parent (from @ContextHierarchy)
- activeProfiles (from @ActiveProfiles)
- propertySourceDescriptors (from @TestPropertySource)
- propertySourceProperties (from @TestPropertySource)
- resourceBasePath (from @WebAppConfiguration)
application context 를 매번 새로 로드하게 되면 그만큼 테스크 구성이 오래걸리므로, 테스트 수행 시간에 영향을 준다.
대표적으로, context caching 이 되지 않는 경우에 대한 예시를 살펴보도록 하자.
context caching 이 되지 않는 케이스 예시
예시1. profile 이 다른 경우
만약 아래와 같이, active profile가 서로 다르다면 서로 다른 application context 를 로드하게 된다.
@ActiveProfiles("dev")
@SpringBootTest
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
class DevTest
@ActiveProfiles("local")
@SpringBootTest
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
class LocalTest
예시2. mock bean 을 사용하는 경우
@SpringBootTest
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
class TestMockTest(
private val testService: TestService,
@MockBean
private var testComponent: TestComponent
)
예시3. property source 를 추가한 경우
@SpringBootTest
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
@TestPropertySource(properties = ["test.value=mocked", "test.value2=mocked2"])
class TestMockTest(
private val testService: TestService,
private var testComponent: TestComponent
)
해결책
1. test 시 사용되는 context 를 하나로 관리
- 하나의 annotation 으로 관리
@SpringBootTest
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
@ActiveProfiles("local")
annotation class IntegrationTest()
- abstract class 로 관리
@SpringBootTest
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
@ActiveProfiles("local")
abstract class IntegrationTest
2. mockBean 사용 자제하기
- test configuration 을 사용하기
이 경우 주의할 점이 있다.
- 각 테스트에 inner class 에 test configuration 을 사용하는 경우, context caching 이 이루어지지 않는다.
- IntegrationTest 를 사용하는 모든 테스트 클래스의 TestComponent 가 mock 으로 등록되기 때문에, 명확성이 떨어진다.
@TestConfiguration
class TestConfig {
@Bean
@Primary
fun testComponent(): TestComponent {
return mockk()
}
}
@SpringBootTest
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
@ActiveProfiles("local")
@Import(TestConfig::class)
abstract class IntegrationTest
// 아래의 test component 는 mock 객체이다.
class TestMockTest(
private val testService: TestService,
private val testComponent: TestComponent
):IntegrationTest()
- 테스트 대상 (sut) 을 조립하기
아래와 같이 mock 으로 만들 대상 객체만 mock 객체로 생성 후, sut 를 생성할 수 있다.
class TestMockTest:IntegrationTest() {
private lateinit var testService: TestService
private lateinit var testComponent: TestComponent
@BeforeEach
fun setUp() {
testComponent = mockk<TestComponent>()
testService = TestService(testComponent)
}
@Test
fun test() {
every { testComponent.test() } returns "test"
testService.test()
}
}