본문 바로가기
스프링부트와 AWS로 혼자 구현하는 웹서비스

스프링 부트 기존 테스트에 시큐리티 적용하기 (feat. 책과 달리 테스트 4개 실패하는 이유)

by 초이사 2023. 3. 8.

이 글은 '스프링부트와 AWS로 혼자 구현하는 웹 서비스 - 이동욱(jojoldu)'을 공부하며 작성한 글로 생략된 내용은 책을 구매해서 확인하는 것을 권장합니다.

참고 소스코드 깃허브 https://github.com/jojoldu/freelec-springboot2-webservice

http://www.yes24.com/Product/Goods/83849117

 

스프링 부트와 AWS로 혼자 구현하는 웹 서비스 - YES24

가장 빠르고 쉽게 웹 서비스의 모든 과정을 경험한다. 경험이 실력이 되는 순간!이 책은 제목 그대로 스프링 부트와 AWS로 웹 서비스를 구현한다. JPA와 JUnit 테스트, 그레이들, 머스테치, 스프링

www.yes24.com

 

책의 5장에 해당하는 내용 중 이전에 작성한 테스트에 시큐리티를 적용하는 것에 대한 과정을 정리하고자 한다. 

 

 

기존테스트에 시큐리티를 적용하는 이유

시큐리티 옵션이 활성화되면서 인증받은 사용자만 API를 호출할 수 있게  바뀌었기 때문에 실패한다.

-> 이전에 작성한 테스트 코드를 인증에 대한 권한이 있는 사용자(인증받은 사용자)가 호출한  것처럼 실행하도록 변경해주어야 한다.

 

이전에 작성한 테스트들을 한 번에 돌려보면( test폴더에서 오른쪽 마우스 클릭 -> Run 'All test' 실행)

전체 테스트 결과1

롬북을 제외한 테스트는 모두 실패한다고 책에 나와있지만 돌려보니 4개의 테스트만 실패했다.???!!?

 

책에서는 다음과 같이 실패하는 이유가 나와있다.

오류를 보면 소셜로그인 관련 설정값들이 있어야 CustomOAuth2UserService를 생성할 수 있는데, 그 값이 없기에 관련 오류가 발생한다.

test 폴더를 실행할 때,

test에 application이 없다면 main의 설정을 그대로 가져오는데, 이때 가져오는 범위는 application.properties까지로 application-oauth.prorperties는 해당되지 않기 때문이다. 이 때문에 테스트 실행시 소셜 로그인 관련 값들을 받아오지 못해 에러가 발생한다.

라고 나와있지만 실제로 돌려보니 test에서도소셜 로그인 관련 설정값들을 인식하고 있었다. (그래서 4개의 테스트만 실패)

 

구글링에서도 따로 언급이 없어 저자의 프로젝트에 들어가서 이슈를 검색해보았다.같은 문제에 대한 이슈를 올려주신 분이 계셔서 원인을 파악할 수 있었다.

 

책과 달리 테스트 4개 실패하는 이유

실제로 test 폴더에서는 application.properties까지만 인식하는데, 이전의 책에 나온 과정대로 코드를 작성했다면  main의 application.properties가 application-oauth.properties를 포함하도록

spring.profiles.include=oauth

위 코드를 추가로 작성해두었다. 이 때문에 test에서도 application-oauth.properties이 불러와진다.그래서 책에 나온 첫번째 문제인 CustomOAuth2UserService를 찾을 수 없는 문제는 test에서도 소셜 로그인 설정 관련 값들을 불러오기 때문에, 해결 가능하다. -> 롬북제외 모든 테스트가 아닌 4개의 테스트만 실패 

 

 

 

302 Status Code 문제

Posts_등록된다() 테스트 로그

Posts_등록된다() 테스트 로그를 보면 응답 결과로 200(정상 응답) Status를 원했는데, 결과는 302(리다이렉션 응답) Status Code가 와서 실패했다고 나와있다. 

스프링 시큐리티 설정으로 인해 인증되지 않은 사용자의 요청은 이동시키기 때문에, 임의로 인증된 사용자를 추가해 API를 요청하도록 수정해야 한다.

 

build.gradle에 의존성 추가

스프링 시큐리티 테스트를 위한 여러 도구를 지원하는 의존성

//스프링 시큐리티 테스트를 위한 도구 지원
testImplementation('org.springframework.security:spring-security-test')

 

PostsApiControllerTest 수정

 @Test
 //임의의 사용자를 만들어서 사용 - ROLE_USER 권한을 가진 사용자가 API를 요청하는 효과
 @WithMockUser(roles = "USER")
 public void Posts_등록된다() throws Exception {
...
@Test
@WithMockUser(roles = "USER")
public void Posts_수정된다() throws Exception{
...
    }

}

@WithMockUser(roles="USER"): 인증된 가짜 사용자를 만들어서 사용한다. (여기서는 ROLE_USER 권한을 가진 사용자가 API를 요청하는 것과 같은 효과를 가진다.), MockMvc에서만 작동하기 때문에 @SpringBootTest에서 MockMvc를 사용하도록 추가 수정 필요

 

@SpringBootTest에서 MockMvc를 사용하도록 추가 수정

@ExtendWith(SpringExtension.class)
//HelloControllerTest와 달리 @WebMvcTest 사용x -> @WebMvcTest는 JPA 기능은 작동하지 않기 때문
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PostsApiControllerTest {
	
    ...
    @Autowired
    private WebApplicationContext context;

    private MockMvc mvc;

    @BeforeEach
    public void setUp() {
        mvc = MockMvcBuilders
                .webAppContextSetup(context)
                .apply(springSecurity())
                .build();
    }
    
	...
    @Test
    //임의의 사용자를 만들어서 사용 - ROLE_USER 권한을 가진 사용자가 API를 요청하는 효과
    @WithMockUser(roles = "USER")
    public void Posts_등록된다() throws Exception {
        ...

        //when
        mvc.perform(post(url)
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .content(new ObjectMapper().writeValueAsString(requestDto)))
                .andExpect(status().isOk());

        //then
        List<Posts> all = postsRepository.findAll();
        assertThat(all.get(0).getTitle()).isEqualTo(title);
        assertThat(all.get(0).getContent()).isEqualTo(content);

    }

    @Test
    @WithMockUser(roles = "USER")
    public void Posts_수정된다() throws Exception{
       	...

        //when
        mvc.perform(put(url)
                        .contentType(MediaType.APPLICATION_JSON_UTF8)
                        .content(new ObjectMapper().writeValueAsString(requestDto)))
                        .andExpect(status().isOk());


        //then
        List<Posts> all = postsRepository.findAll();
        assertThat(all.get(0).getTitle()).isEqualTo(expectedTitle);
        assertThat(all.get(0).getContent()).isEqualTo(expectedContent);
    }

}

@BeforeEach: 매번 테스트가 시작되기 전에 MockMvc 인스턴스를 생성한다.

책에서는 @Before라고 되어있는데, Junit5가 되면서 변경되었다.

mvc.perform: 생성된 Mockmvc를 통해서 API를 테스트 한다. 본문(Body 영역)은 문자열 표현을 위해 ObjectMapper를 통해 문자열 JSON으로 변환한다.

 

전체 테스트 다시 실행

전체 테스트 결과2

PostApiControllTest도 성공하는 것을 확인할 수 있다.

 

 

 

@WebMvcTest에서 CustomOAuthUserService를 찾을 수 없는 문제

hello가_리턴된다() 테스트 로그1

hello가 리턴된다() 테스트 로그를 보면 첫번째 문제와 마찬가지로 CustomOAuth2UserService를 찾을 수 없는 문제이다. 다른 테스트와 달리 여기서는 문제가 생기는 이유는 여기서는 @WebMvcTest를 사용하고 있기 때문이다.

@WebMvcTest는 CustomOAuth2UserService를 스캔하지 않아 문제가 발생한다.

 

@WebMvcTest

스캔 대상: WebSecurityConfigureAdapter, WebMvcConfigurer를 비롯한 @ControllerAdvice, @Controller

스캔 대상x: @Repository, @Service, @Component

-> SecurityConfig는 읽지만 SecurityConfig 생성을 위해 필요한 CustomOAuth2UserService는 읽어오지 못하기 때문에 에러 발생

 

문제해결을 위해 스캔 대상에서 SecurityConfig를 제거한다.

HelloControllerTest 수정

@WebMvcTest(controllers = HelloController.class,
excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
        classes = SecurityConfig.class)
})

 

@WithMockUser를 사용해서 위에서처럼 인증된 가짜 사용자를 생성한다.

@WithMockUser(roles = "USER")
@Test
public void hello가_리턴된다() throws Exception {
    ...
    
@WithMockUser(roles = "USER")
@Test
public void helloDto가_리턴된다() throws Exception {
	...
}

 

여기까지 수정하고 테스트 해보면

hello가_리턴된다() 테스트 로그2

위와 같은 에러가 발생한다. @EnableJpaAuditing로 인해 발생하는 에러로 @EnableJpaAuditing 사용을 위해서는 최소 하나의 @Entity 클래스가 필요한데, @WebMvcTest다 보니 없다.

@EnableJpaAuditing이 @SpringBootApplication와 함께 있어서 @WebMvcTest에서도 스캔하게 되기 때문에, 이를 막기 위해 @EnableJpaAuditing과 @SpringBootApplication 둘을 분리해줘야 한다.

 

Application 수정

@EnableJpaAuditing 제거

 

JpaConfig 생성 (config/)

@EnableJpaAuditing 추가

@Configuration
//JPA Auditing 활성화
@EnableJpaAuditing
public class JpaConfig {
}

 

이후 전체 테스트를 실행해보면 모두 성공하는 것을 확인할 수 있다.

전체 테스트 결과3

댓글