인프런 커뮤니티 질문&답변

잉앙잉님의 프로필 이미지
잉앙잉

작성한 질문수

토비의 스프링 부트 - 이해와 원리

[DataSource 자동 구성 클래스 테스트] properties와 yml의 차이점 관련 질문입니다.

해결된 질문

작성

·

419

·

수정됨

0

안녕하세요, 강사님.

섹션 9-2, DataSource 자동 구성 클래스에서, connect() 을 테스트하는 코드에서 yml파일을 이용했을 때, 테스트 실패를 하여 질문글 드립니다.


먼저 강의와 동일하게 properties 파일을 이용한다면 정상적으로 테스트가 통과됩니다.

application.properties

DataSourceTest


평소에 yml 파일을 더 선호하여, yml 파일로 변경한 후, 테스트 코드 상에서 @TestPropertySource("classpath:/application.yml")classpathproperties에서 yml로 바꿨을 뿐, 기존과 동일한 환경에서 테스트를 진행해봤습니다.


 

application.yml

DataSourceTest

하지만, 아래와 같은 에러 로그가 발생하며 테스트에 실패하게 됩니다.

 

에러 로그

java.lang.IllegalStateException: Failed to load ApplicationContext

	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:98)
	at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:124)
	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:118)
	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
	at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:248)
	at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:138)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$8(ClassBasedTestDescriptor.java:363)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:368)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$9(ClassBasedTestDescriptor.java:363)
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
	at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177)
	at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
	at java.base/java.util.stream.StreamSpliterators$WrappingSpliterator.forEachRemaining(StreamSpliterators.java:312)
	at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:735)
	at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:734)
	at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:658)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestInstancePostProcessors(ClassBasedTestDescriptor.java:362)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$instantiateAndPostProcessTestInstance$6(ClassBasedTestDescriptor.java:283)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:282)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$4(ClassBasedTestDescriptor.java:272)
	at java.base/java.util.Optional.orElseGet(Optional.java:369)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$5(ClassBasedTestDescriptor.java:271)
	at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:31)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare$0(TestMethodTestDescriptor.java:102)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:101)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:66)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$2(NodeTestTask.java:123)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:123)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:90)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
	at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
	at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'tobyspring.config.autoconfig.ServerProperties': Initialization of bean failed; nested exception is org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'server' to tobyspring.config.autoconfig.ServerProperties
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:628)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:955)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:920)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:127)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:60)
	at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.delegateLoading(AbstractDelegatingSmartContextLoader.java:276)
	at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:244)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:141)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:90)
	... 71 more
Caused by: org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'server' to tobyspring.config.autoconfig.ServerProperties
	at org.springframework.boot.context.properties.bind.Binder.handleBindError(Binder.java:387)
	at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:347)
	at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:332)
	at org.springframework.boot.context.properties.bind.Binder.bindOrCreate(Binder.java:324)
	at org.springframework.boot.context.properties.bind.Binder.bindOrCreate(Binder.java:293)
	at org.springframework.boot.context.properties.bind.Binder.bindOrCreate(Binder.java:278)
	at tobyspring.config.autoconfig.PropertyPostProcessorConfig$1.postProcessAfterInitialization(PropertyPostProcessorConfig.java:30)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:455)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1808)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:620)
	... 85 more
Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [tobyspring.config.autoconfig.ServerProperties]
	at org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:118)
	at org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:100)
	at org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:92)
	at org.springframework.boot.context.properties.bind.Binder.bindProperty(Binder.java:459)
	at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:403)
	at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:343)
	... 93 more

 

에러 로그를 확인한 후, ServerProperties가 제대로 바인딩 되지 않음을 확인하였습니다.

바인딩이 테스트 환경이 아닌, 일반 서버를 구동할 때도 제대로 안되는건가? 라는 의구심에 로그를 찍어보며 이를 확인해봤습니다.

 


로그 출력

PropertyPostProcessorConfig

TomcatWebServerConfig

DataSourceConfig

로그 출력 확인 (일반 서버 구동)

테스트 환경이 아닌 일반 서버를 실행시, 위의 결과와 마찬가지로 제대로 바인딩됨을 확인하였습니다. 따라서, 테스트 코드상에서 바인딩이 제대로 이뤄지지 않음을 알게 되었고, 스프링부트 레퍼런스를 찾아보니 yml 파일은 @PropertySource@TestPropertySource를 통해 로딩되지 않음을 확인하였습니다.

image(https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#boot-features-external-config-yaml-shortcomings

 

이를 해결하기 위해, 구글링을 통해 해결방법을 참고하여 아래와 같이 변경하였더니, 테스트가 제대로 통과됨을 확인하였습니다.

(참고링크 : https://www.inflearn.com/questions/293325/testpropertysource%EC%A7%88%EB%AC%B8%EC%9E%85%EB%8B%88%EB%8B%A4 )

하지만 이전 토비님의 질문 답변 중, @SpringBootTest@ExtendWith + @ContextConfiguration 의 차이점에 대한 글을 보게 되었습니다.

(https://www.inflearn.com/questions/834733/springboottest%EC%99%80-extendwith-contextconfiguration-%EC%B0%A8%EC%9D%B4%EA%B0%80-%EA%B6%81%EA%B8%88%ED%95%A9%EB%8B%88%EB%8B%A4)

 

혹시 @ExtendWith + @ContextConfiguration 을 유지한 채, yml 파일을 이용하여 해당 테스트를 통과할 수 있는 방법이 존재하는지 궁금합니다.

 

항상 수준 높은 강의에 감사드립니다.

답변 1

1

토비님의 프로필 이미지
토비
지식공유자

스프링은 자체적으로 yml 파일에 담긴 프로퍼티를 지원하지 않습니다. 스프링부트가 추가한 프로퍼티 리더를 통해서 yml이 부트에서 지원된 것이지요. 테스트에서 마찬가지로 스프링 부트의 테스트 애노테이션을 사용하면 yml이 기본적으로 지원되는데, 이것 역시 스프링의 테스트 기능을 부트가 확장하면서 여러가지 추가한 초기 환경 정보 확장 덕분에 가능해진 것입니다.

강의 중간에 사용했던 @ExtendWith과 @ContextConfiguration은 스프링 자체의 테스트 기술이고요. @SpringBootTest는 이걸 더 확장해서 테스트를 위한 각종 부트스트래핑을 추가해주는데요. 여기에 yml 테스트 프로퍼티 지원도 포함됩니다.

질문해주신 스프링의 테스트 애노테이션을 사용하면서 yml로부터 프로퍼티를 읽어오게 하려면 스프링 부트의 테스트 확장 기능 중 일부를 추가해야할테데요. 이게 간단해보이지는 않네요. 당연히 가능은 합니다. 결국 스프링 부트의 테스트 확장 기능을 모두 파헤쳐보면 되긴 하겠죠.

나중에 스프링 테스트에 관한 강의를 만들면 그때 부트의 테스트 확장 기능을 하나씩 직접 따라가보면서 어떤 기능이 제공되는지 살펴볼 생각인데요.. 어쨌든 흥미로운 질문을 주셨으니 제가 한번 시간을 내서 yml 프로퍼티를 사용하는 기능을 기본 스프링 테스트 애노테이션만 사용했을 때 어떻게 적용할 수 있는지 분석해서 알려드리겠습니다. 재밌는 주제라서 영상을 하나 만들어서 강의에 추가하든가, 제 유튭 채널을 통해서 공개해도 좋을 것 같습니다.

잉앙잉님의 프로필 이미지
잉앙잉
질문자

감사합니다!

평소에 흔히 쓰던 yml 프로퍼티 지원도 모두 스프링부트로부터 얻은 이점이였군요.

토비님의 강의 덕분에 평소엔 그려러니 지나쳤던게 다시금 보이기 시작했습니다ㅎㅎ

잉앙잉님의 프로필 이미지
잉앙잉

작성한 질문수

질문하기