2024. 7. 15. 03:54ㆍBackend 취업준비/Spring
스프링 시큐리티가 적용된 곳에서 @SpringBootTest 를 사용하여 controller계층을 테스트 해보았었는데 이것을 @WebMveTest로 단위테스트로 리팩토링 하면서 많은 오류들을 마주하였어서 기록합니다
먼저 단순히 @SpringBootTest 어노테이션을 @WebMveTest로 바꾸어 주게 되면 다음과 같은 오류 메세지를 확인할 수 있다
주요 오류 메세지
- org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaAuditingHandler': Cannot resolve reference to bean 'jpaMappingContext' while setting constructor argument
- Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaMappingContext': JPA metamodel must not be empty
- java.lang.IllegalStateException: Failed to load ApplicationContext
@EnableJpaAuditing을 다음과 같이 어플리케이션 클래스 위에 선언하고 단위테스트를 진행하려 해서 문제가 생겼던것

오류메세지와 함께 원인을 분석
- 테스트를 실행할 때도 항상 어플리케이션 클래스를 로드하는데 이에 실패함. (Failed to load ApplicationContext)
- @WebMvcTest는 웹 계층에 대한 테스트만 수행하고 JPA 관련 빈들을 로드하지 않고 어플리케이션을 실행시킴
- EnableJpaAuditing을 어플리케이션 클래스 위에 선언하면 테스트를 수행하여 어플리케이션을 실행시킬때 JPA 관련 빈들을 찾게 되는데, 이 때 @WebMvcTest로 테스트를 수행하면 앞서 말했듯이 JPA 관련 빈들을 로드하지 않기 때문에 오류가 발생함(JPA metamodel must not be empty)
이 문제를 해결하기 위해서는 @WebMvcTest와 같은 슬라이스 테스트에서는 @EnableJpaAuditing이 테스트에 영향을 미치지 않도록 구성하거나, JPA 관련 빈을 직접 주입해주어야함
- 다음과 같이 config파일로 별도로 구성하여 간단히 해결 가능하다

- @WebMvcTest를 사용하는 테스트 클래스에 @MockBean(JpaMetamodelMappingContext.class)를 사용하여 메타모델 맵핑 컨텍스트를 의도적으로 주입해주어도 해결 가능하나, 권장되진 않는다.
더욱 자세한 원리가 궁금하다면
https://hyewoncc.github.io/jpa-auditing-and-ci/
첫 문제를 해결했다. 다음은 이런 오류 메세지가 나오게 된다

분석해보면 get요청에 대한 테스트코드는 성공했지만, 다른 요청(post,patch,delete) 들은 실패했다.
주요 오류메세지
- No value at JSON path
- java.lang.IllegalArgumentException: json can not be null or empty
- MockHttpServletResponse 403 Forbidden, body가 존재하지 않음

원인분석
- CSRF 보호는 스프링 시큐리티에서 기본적으로 활성화됨
- 따라서 get요청 외의 요청엔 csrf토큰이 필요함
- 다음과 같이 설정하면 됨
ResultActions resultActions = mockMvc.perform( post("url").with(csrf()))
스피링 시큐리티 csrf에 대한 자세한 내용은 다음 링크 참고
https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html#csrf-components
의문이 있다. 시큐리티 설정을 할 때 다음과 같이 csrf보호를 비활성화 했었는데??

원인은 @WebMvcTest에 존재했다.
- @WebMvcTest를 활용하면 직접 커스텀한 Spring Security Configuration이 사용되지 않음.
- 대신 Spring Security와 MockMvc에 대한 auto-configure 한다. Spring Security가 자동으로 구성하는 Configuration 파일들을 불러와서 사용한다
- 해당 내용 출처https://docs.spring.io/spring-boot/api/java/org/springframework/boot/test/autoconfigure/web/servlet/WebMvcTest.html
- 자동으로 구성되는 많은 클래스 중 SpringBootWebSecurityConfiguration를 살펴보면 아래와 같다.
@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
@ConditionalOnWebApplication(type = Type.SERVLET)
class SpringBootWebSecurityConfiguration {
@Bean
@Order(SecurityProperties.BASIC_AUTH_ORDER)
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
return http.build();
}
}
모든 요청에 대하여 아무 권한을 가지고 있다면 허용되도록 기본적으로 설정되어 있다. 또한 csrf비활성화는 적용되어 있지 않다.
위와 같은 특성 때문에 로그인이 필요하지 않은 회원가입 요청 테스트 메서드에서 401 Unauthorized도 났다.
회원가입 요청을 보내는 url을 시큐리티 설정을 통하여 권한이 없어도 접근 가능하도록 설정하였지만, @WebMvcTest 특성 때문에 마찬가지로 오류가 났다. @WithMockUser로 권한을 부여 하여서 해결하였다.
그런데 이 방법이 옳은 방법인가? 로그인이 필요하지 않은 회원가입 요청을 테스트 하는 메서드를 왜 권한이 있다고 가정하고 테스트 해야하지?
직접 커스텀한 시큐리티 설정을 테스트 환경으로 가져오고 싶다
@WebMvcTest(controllers = UserController.class)
@ImportAutoConfiguration(SecurityConfig.class)
public class UserControllerTest {
// 테스트 코드 작성
}
@WebMvcTest에서 시큐리티 설정을 포함시키고 싶다면 @ImportAutoConfiguration 애너테이션을 사용하여 명시적으로 시큐리티 설정 클래스를 추가할 수 있다.
https://github.com/spring-projects/spring-boot/issues/31162
이 과정에서 다음과 같은 오류가 나며 모든 테스트 케이스에서 실패했다.

주요 오류메세지
- No qualifying bean of type 'org.springframework.boot.autoconfigure.h2.H2ConsoleProperties' available
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.boot.autoconfigure.h2.H2ConsoleProperties' available
이유는 다음과 같이 설정했기 때문이다

H2ConsoleProperties 빈 미생성
@WebMvcTest는 웹 계층만 테스트하기 때문에 데이터베이스 관련 빈(H2ConsoleProperties 등)이 생성되지 않는다.
따라서 toH2Console() 메서드를 호출하면 H2 관련 빈을 찾을 수 없기 때문에 에러가 발생
toH2Console() 메서드 호출 제거하면
toH2Console() 메서드를 제거하면 H2 콘솔 관련 빈을 참조하지 않게 되어 H2ConsoleProperties 빈을 찾지 않아도 되므로 에러가 발생하지 않는다.
'Backend 취업준비 > Spring' 카테고리의 다른 글
스프링MVC, DispatcherServlet, ArgumentResolver, HttpMessageConverter (0) | 2024.08.15 |
---|---|
AOP를 활용한 api 중복 요청 방지 (따닥 방지) (0) | 2024.07.22 |
@Transactional, 영속성 컨텍스트 (2) | 2024.03.23 |
엔티티와 @RequestBody를 쓰는 DTO에서 기본 생성자, getter/setter의 필요성 (리플랙션) (0) | 2024.03.22 |
스프링 시큐리티 (0) | 2024.03.20 |