문제&해결

@Value가 단위 테스트에서 동작하지 않는 문제 (생성자 주입, @WebMvcTest)

mint* 2024. 3. 11. 18:24
728x90

문제

문제 상황

@Value 어노테이션을 사용하여 yml 파일의 프로퍼티 값을 secretKey 필드에 주입하려고 한다.

서버 실행시 잘 작동했지만, 테스트 수행시 secretKey 필드가 초기화되지 않았다는 에러가 발생한다.

Request processing failed; nested exception is kotlin.UninitializedPropertyAccessException: lateinit property secretKey has not been initialized

 

문제 분석

@Value는 Spring이 해당 클래스의 인스턴스를 생성하고 관리할때, 프로퍼티 파일(yml)에 정의된 값을 자동으로 주입한다.

 

하지만 @WebMvcTest와 같은 단위 테스트는 특정 Controller만 로드하기 때문에

@Value로 직접 인스턴스를 생성하거나 컨텍스트에 등록되지 않은 클래스에서는 값을 자동으로 주입받을 수 없다.

 

해결 방법

1. 필드에서 @Value를 통해 값을 주입받는 것이 아닌, 생성자 주입을 이용한다.

2. TestConfig에서 @Value로 값을 주입받아 해당 인스턴스를 생성한다.

3. TestConfig 클래스를 테스트 컨텍스트에 포함시켜 @Autowired로 인스턴스를 주입한다.

 

코드로 알아보기

기존 코드

컴포넌트

@Component
class EncryptComponent {
    @Value("\${encrypt.secretKey}")
    private var secretKey: String) // 주입받은 값

    private val encoder = Base64.getEncoder()
    private val decoder = Base64.getDecoder()
..
}

값을 @Value로 주입받는다.

단위 테스트 코드

@WebMvcTest(LoanRequestController::class)
internal class LoanRequestControllerTest {
	...
    private lateinit var encryptComponent: EncryptComponent // 여기서 문제 발생
	...
    @BeforeEach
    fun init() {
        generateKey = GenerateKey()
		
        loanRequestServiceImpl = LoanRequestServiceImpl(
            generateKey, userInfoRepository, encryptComponent
        )
		...
    }

 

해결 코드

생성자로 값을 주입받고, TestConfig로부터 @Value로 값을 가져와서 속성 값을 가져와 반환한다.

 

컴포넌트 : 값을 생성자로 주입받도록 변경

@Component
class EncryptComponent(
    @Value("\${encrypt.secretKey}")
    private var secretKey: String
) {

    private val encoder = Base64.getEncoder()
    private val decoder = Base64.getDecoder()
    ...

 

testConfig에서 속성 값 주입

@Configuration
class TestConfig {

    @Bean
    fun encryptComponent(@Value("\${encrypt.secretKey}") secretKey: String): EncryptComponent {
        return EncryptComponent(secretKey)
    }
}
testConfig에서 속성값을 주입할 수 있는 이유는 testConfig는 @Configuration로 인해 Spring의 설정 클래스로 인식된다.
이 안에서 선언된 메서드는 빈 정의를 생성하며 Spring IoC 컨테이너에 의해 관리된다.

Spring은 Spring이 관리하는 빈들 중 @Value 어노테이션이 붙을 경우 값 주입을 수행한다.

 

테스트 컨텍스트에 추가

@WebMvcTest(LoanRequestController::class) 
@ContextConfiguration(classes = [TestConfig::class]) // 추가
internal class LoanRequestControllerTest {

    @Autowired
    private lateinit var encryptComponent: EncryptComponent

	// init()에서 encryptComponent 부분 삭제
    @BeforeEach
    fun init() {
        generateKey = GenerateKey()

        loanRequestServiceImpl = LoanRequestServiceImpl(
            generateKey, userInfoRepository, encryptComponent
        )

    }

 

728x90