1. 무엇으로 configuration 을 구성할까?
참고로 조금 더 자세한 추가적인 내용은 공식문서에 있습니다.
1편에서 소개한 TestContextBootstrapper 구현체 SpringBootTestContextBootstrapper 는 기본적으로 별다른 조건이 없다면 @SpringBootConfiguration 어노테이션이 붙은 클래스를 찾아서 context 를 구성한다.
즉 @SpringBootApplication 어노테이션 내에 @SpringBootConfiguration 어노테이션을 포함하기 때문에 별다른 조건이 없는 경우 전체 컨테이너 환경이 구성되는 것이다.
// @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
하지만, 위에서 언급한 것처럼 특정 조건이 있을 때는 context 가 다르게 동작할 수 있다.
TestContextBootStrapper 에서 Configuration class 를 가져올 때 아래의 동작을한다.
- 수행되는 테스트 클래스 내부에 inner class 에 @Configuration 이 있는 경우
- 해당 Configuration 만을 사용한다.
- inner class 에 @TestConfiguration 이 있는 경우, SpringBootConfiguration 과 병합한다.
따라서, 대표적으로 아래의 케이스에서 다르게 동작한다.
- nested @Configuration
- @TestConfiguration
1.1 Nested @Configuration
nested configuration 클래스가 있는 경우, 해당 configuration 만 적용된다.
아예 spring boot context 자체가 생성되지 않고, nested configuration 으로 생성되는 것이다.
따라서, 아래의 코드는 Configuration 클래스에 TestService 가 정의되지 않았기 때문에 Bean 을 찾지 못해서 에러가 난다.
// src/main 에 구현된 테스트 대상 클래스
@Service
class TestService(
private val testComponent: TestComponent
)
// 테스트 코드
@SpringBootTest
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
class TestMockTest(
private val testService: TestService,
) {
@Test
fun test() {
testService.test()
}
@Configuration
class TestConfig {
@Bean
@Primary
fun testComponent(): TestComponent {
return mockk()
}
}
}
참고로, nested configuration 에 대해서만 이러한 동작을 하게 되고 @Import 를 통해서 구성하는 경우에는 SpringBootContext 에 추가적으로 적용된다.
@SpringBootTest
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
@Import(TestConfig::class)
class TestMockTest(
private val testService: TestService,
) {
@Test
fun test() {
testService.test()
}
}
// 별도 파일 분리
@Configuration
class TestConfig {
@Bean
@Primary
fun testComponent(): TestComponent {
return mockk(relaxed = true)
}
}
참고로 이 자세한 동작은 SpringBootTestContextBootstrapper.getOrFindConfigurationClasses 메서드 코드를 살펴보면 알 수 있다.
1.2 @TestConfiguration
테스트에만 사용되는 Configuration 으로, 다른 Context 에 추가로 적용되는 Configuration 이다.
아래 코드의 경우, 기존 SpringBootContext 에 추가로 TestComponent 빈을 mockk 으로 등록한다.
이 경우에는 nested configuration 과 다르게 TestService 가 잘 Inject 된다.
@SpringBootTest
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
class TestMockTest(
private val testService: TestService,
) {
@Test
fun test() {
testService.test()
}
@TestConfiguration
class TestConfig {
@Bean
@Primary
fun testComponent(): TestComponent {
return mockk(relaxed = true)
}
}
}
2. Property
기본적으로는 property 는 main 하위의 application.properties, application.yml 을 읽게 된다.
2.1 test 하위의 application.yml
test 하위의 application yml 은 main 의 application.yml 을 덮어쓰게 된다.
즉, main 의 application yml 은 무시하기 때문에 필요한 모든 property 를 test 하위의 application.yml 에 명시해야함.
아래 예시를 보자.
// main 에 등록
// src/main/resources/application.yml
test:
value: app
value2: app2
// test 에 등록
// src/test/resources/application.yml
test:
value: test
value2: test2
@Service
class TestService(
@Value("\\${test.value}")
private val testValue: String,
@Value("\\${test.value2}")
private val testValue2: String,
)
위의 경우, test 수행시에 test 디렉터리에 작성한 application.yml 을 읽게 된다.
하지만 이 경우에는 test 하위의 application.yml 만 읽기 때문에, 아래처럼 value2 값을 빠뜨리면 서버가 기동되지 않는다.
// src/test/resources/application.yml
test:
value: test
2.2 profile 에 따른 동작
active profiles 를 정의하게 되면, test 하위의 application-{profile}.yml 파일을 읽게 된다.
이 경우에는 기본 application.yml 파일과 병합하게 되는데, 기존 application.yml 파일의 프로퍼티를 기본으로 하고, test 하위의 yml 파일이 재정의 할 수 있게 된다.
// src/test/resources/application-{profile}.yml
test:
value: test
@ActiveProfiles("test")
@SpringBootTest
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
class TestServicePropertyTest(
private val testService: TestService
)
2.3 SpringBootTest 의 properties attribute 사용
properties 를 오버라이딩 할 수 있으며, yml 에 정의된 것 보다 우선권을 가지게 된다.
@SpringBootTest(properties = ["test.value=hello"])
참고로 이 경우 test 의 application context caching 이 이루어지지 않기 때문에, application context 를 추가로 로드해야한다.
Context Caching 은 3편에서 추가적으로 다룬다.
스프링 부트 테스트(3) - context caching
2.4 @TestPropertySource
위의 properties attribute 와 비슷하게 @TestPropertySource 어노테이션을 사용할 수도 있다.
이 경우에도 마찬가지로 context caching 이 되지 않는다.
@SpringBootTest
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
@TestPropertySource(properties = ["datasource.connection-pool=3"])
class TestMockTest(
private val testService: TestService,
){
@Test
fun test() {
testService.test()
}
}
2.5 property import 하기
그 외에도 property 를 import 할 수도 있다.
아래처럼 test 하위의 application.yml 에서 main 의 application.yml 을 import 할 수 있는데, 이 경우 import 한 yml 파일이 기존의 프로퍼티가 선언된 순서와 관계 없이 오버라이딩 하게 된다
// src/test/resources/application.yml
spring:
application:
name: junit
config:
import:
- db-core.yml
// 여기서 정의한 값이 아닌 db-core.yml 값을 사용하게 된다.
test:
value: test
value2: test2
// db-core.yml
test:
value: db
value2: db2 // 만약 이 값이 생략된다면, application.yml 값을 사용하게 된다.
'스프링부트' 카테고리의 다른 글
@Async 를 통해 비동기 호출을 해보자 (1) | 2024.11.10 |
---|---|
JPA 1차캐시의 동작 방식 (0) | 2024.07.28 |
스프링부트 테스트(1) - 동작원리와 어노테이션 친해지기 (1) | 2024.07.23 |
AOP 활용 (feat. jpa 호출 로깅) (0) | 2024.06.06 |
스프링 AOP - 톺아보기 (1) | 2024.06.06 |