Spring Security 기반의 테스트 코드 작성하기

  • 목표

    • HelloControllerTest (페이지 접근)
    • PostsAPIControllerTest (API 호출 기능)
    • Spring Security로 인한 접근 권한에 연관된 테스트는 Security 옵션이 필요
    • 따라서 작성한 전체 테스트가 정상적으로 수행

Tasks -> verification -> test (전체 테스트)
전체 테스트 확인

  • 기존에 SpringSecurity 설정이 되어 있지 않은 상태에서 작성된 테스트 코드는 Security 적용 시 오류가 발생할 수 있다.

API 호출 및 페이지 접근 Controller 테스트 오류 해결하기

이슈 1. CustomOAuth2UserService를 찾을 수 없습니다.

  • CustomOAuth2UserService
    • 소셜로그인 관련 설정값에 따른 실행
      • 기본적으로 application.properties 파일을 src/main, src/test 모두 읽어서 사용한다.
      • src/test/resources에 application.properties가 없을 경우 src/main/resources에 있는 application.properties를 자동으로 읽는다.
      • src/main/resources/application-oauth.properties는 읽지 못한다.
      • 즉, application-oauth.properties가 test쪽에 파일이 없기때문에 발생하는 오류이다.
      • 해결책은 application.properties에 오류만 안나도록 설정 값을 임의로 넣는다.

test용 설정 파일

  • 설정 파일은 application.yml로 작성하였다.
# server setting
server:
  port: 8085

# jpa setting
spring:
  profiles:
    include: oauth # oauth 관련 properties 추가
  session:
    store-type: jdbc
  h2:
    console:
      enabled: true
  jpa:
    show_sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect
  # Test 용 security 설정
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: test
            client-secret: test
            scope:
              - profile
              - email

이슈 2. 302 Status Code

  • PostsAPIController 테스트

    • Spring Security 설정으로 인한 인증되지 않은 사용자의 요청으로 인해 리다이렉션 응답(302 status code)을 주게 된다.
    • 이를 해결하기 위해서는 테스트 메서드에 임의로 인증된 사용자를 추가하여 API만 테스트 할 수 있도록 수정해야 한다.
  • SpringSecurity Test를 위한 spring-security-test 의존성 추가

dependencies {
    // ...
    testCompile('org.springframework.security:spring-security-test')
}
  • PostsAPIController의 테스트 메서드에 임의 사용자 인증 설정 추가
    1. @WithMockUser(roles="USER")
      • 인증된 임의 사용자를 만들어 사용
      • roles 설정으로 권한 추가가 가능
      • 해당 어노테이션으로 인하여 ROLE_USER 권한을 가진 사용자가 API를 요청하는 것과 동일한 효과를 갖는다.
      • @WithMockUser가 MockMvc에만 작동하기 때문에 MockMvc에서 테스트 할 수 있도록 @Before, mvc.perform 기능을 추가
    2. @Before
      • 매번 테스트가 시작되기 전에 MockMvc 인스턴스를 생성
    3. mvc.perform
      • 생성된 MockMvc를 통해 API를 테스트한다.
      • 본문(Body) 영역은 문자열로 표현하기 위해서 ObjectMapper를 통해 문자열 JSON으로 변환
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PostsAPIControllerTest {
    // ...

    @Autowired
    private WebApplicationContext webApplicationContext;

    private MockMvc mockMvc;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders
                .webAppContextSetup(webApplicationContext)
                .apply(springSecurity())
                .build();
    }

    @Test
    @WithMockUser(roles="USER")
    public void testSave() throws Exception {
        // ...

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

        // ...
    }

    @Test
    @WithMockUser(roles="USER")
    public void testUpdate() throws Exception {
        // ...

        mockMvc.perform(put(url)
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .content(new ObjectMapper().writeValueAsString(requestDto)))
                .andDo(print())
                .andExpect(status().isOk());

        // ...
    }
}

이슈 3. @WebMvcTest에서 CustomOAuth2UserService을 찾을 수 없는 문제

  • HelloControllerTest의 경우 위에서 처리한 방법과는 다르다.
    • 위 이슈를 함으로써 SpringSecurity 설정은 작동했지만 @SpringBootTest와 다르게 @MockMvcTest는 CustomOAuth2UserService를 스캔하지 않는다.
    • @WebMvcTest는 WebSecurityConfigurerAdapter, WebMvcConfigurer를 비롯한 @ControllerAdvice, @Controller를 읽는다.
    • 즉, @Repository, @Service, @Component는 스캔 대상이 아니다.
    • SecurityConfig는 읽었지만 SecurityConfig를 생성하기 위해 필요한 CustomOAuth2UserService는 읽을 수가 없어 오류가 발생했던 것이다.
    • 위 문제를 해결하기 위해서는 스캔 대상에서 SecurityConfig를 제거해야 한다.
@RunWith(SpringRunner.class)
@WebMvcTest(
        controllers = HelloController.class,
        excludeFilters = {
                @ComponentScan.Filter(
                        type = FilterType.ASSIGNABLE_TYPE,
                        classes = SecurityConfig.class
                )
        }
)
public class HelloControllerTest {

    @Test
    @WithMockUser(roles="USER")
    public void return_hello() throws Exception {

    }
    @Test
    @WithMockUser(roles="USER")
    public void return_helloDto() throws Exception {

    }
}
  • 원인 분석
    • @EnableJpaAuditing을 사용하기 위해서는 최소 하나의 @Entity 클래스가 필요하다.
    • @WebMvcTest의 경우 @Entity 클래스가 없다.
    • @EnableJpaAuditing와 @SpringBootApplication 설정을 함께 사용하였기 때문에 @WebMvcTest에서 둘다 읽게 된다.
    • 이 문제를 해결하기 위해서는 @EnableJpaAuditing을 사용하기 위한 JpaConfig 설정 클래스를 새로 작성한다.
// @EnableJpaAuditing 제거
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  • @EnableJpaAuditing 설정을 사용하는 JpaConfig 클래스
    • JPA Auditing 활성화
@Configuration
@EnableJpaAuditing
public class JpaConfig {}
  • 작성한 패키지 구조 및 파일 확인

  • 테스트 코드 패키지 및 파일 확인

정리하기

  • 스프링 부트 통합 개발환경 설정
  • 테스트, JPA 데이터 처리
  • Mustache를 활용한 화면 구성
  • Security와 OAuth2로 인증, 권한을 적용하여 게시판 작성

+ Recent posts