🌱 이번 장의 스터디 범위
- 스프링 시큐리티 적용 시 기존 테스트 코드에서 문제 해결 방법
🌱 기존 테스트에 시큐리티 적용으로 문제가 되는 부분 해결
- 기존에는 바로 API를 호출할 수 있어 테스트 코드 역시 바로 API를 호출하도록 구성하였음
- 하지만, 시큐리티 옵션이 활성화되면 인증된 사용자만 API를 호출할 수 있어
- 기존의 API 테스트 코드들이 모두 인증에 대한 권한을 받지 못하였으므로
- 테스트 코드마다 인증한 사용자가 호출한 것처럼 작동하도록 수정
- [Gradle 탭] - [Tasks] - [verification] - [test]를 선택해 전체 테스트를 수행

- 테스트 실행 시 롬복을 이용한 테스트 외에 스프링을 이용한 테스트는 모두 실패하는 것을 확인 가능

🌱 문제 1. CustomOAuth2UserService을 찾을 수 없음
- "No qualifying bean of type 'com.gagyeong.book.springboot.config.auth.CustomOAuth2UserService'"
- CustomOAuth2UserService를 생성하는데 필요한 소셜 로그인 관련 설정값들이 없기 때문에 발생
- application-oauth.properties에 설정값들을 추가했는데 왜 설정이 없다고 할까?
- 이는 src/ main 환경과 src/ test 환경의 차이 때문. 둘은 본인만의 환경 구성을 가짐
- 다만, src/ main/resources/application.properties가 테스트 코드를 수행할 때도 적용되는 이유는 test에 application.properties가 없으면 main의 설정을 그래도 가져오기 때문임.
- 다만, 자동으로 가져오는 옵션의 범위는 application.properties 파일까지이므로 application-oauth.properties는 test에 파일이 없다고 가져오는 파일이 아님
- 이를 해결하기 위해 테스트 환경을 위한 application.properties를 만들 것
- 실제로 구글 연동까지 진행할 것은 아니므로 가짜 설정값을 등록
// test/resources/application.properties
spring.jpa.show_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.h2.console.enabled=true
spring.session.store-type=jdbc
# Test OAuth
spring.security.oauth2.client.registration.google.client-id=test
spring.security.oauth2.client.registration.google.client-secret=test
spring.security.oauth2.client.registration.google.scope=profile,email
- 다시 gradle로 테스트를 수행해 보면 실패 테스트가 줄어든 것을 확인 가능
🌱 문제 2. 302 Status Code
- 응답의 결과로 200 (정상 응답) status code를 원했지만 결과는 302 (리다이렉션 응답) status code가 와서 실패

- 이는 스프링 시큐리티 설정 때문에 인증되지 않은 사용자의 요청은 이동시키기 때문임
- 이를 해결하기 위해 이런 API 요청은 임의의 인증된 사용자를 추가하여 API만 테스트해 볼 수 있도록 함
- 스프링 시큐리티 테스트를 위한 여러 도구를 지원하는 spring-security-test를 build.gradle에 추가
// build.gradle
testCompile('org.springframework.security:spring-security-test')
- PostsApiControllerTest의 2개 테스트 메소드에 임의의 사용자 인증을 추가
// test/web/PostsApiController.java
@Test
/* @WithMockUser(roles="USER")
: 인증된 모의 (가짜) 사용자를 만들어서 사용
roles에 권한을 추가할 수 있음
즉, 어노테이션으로 인해 ROLE_USER 권한을 가진 사용자가 API를 요청하는 것과 동일한 효과 */
@WithMockUser(roles="USER")
public void Posts_등록된다() throws Exception {
...
}
@Test
@WithMockUser(roles="USER")
public void Posts_수정된다() throws Exception {
...
}
- @WithMockUser가 MockMvc에서만 작동하기 때문에 PostsApiController에 MockMvc를 사용하도록 해야 함
- @SpringBootTest에서 MockMvc를 사용하는 방법 코드로 변경
// test/web/PostsApiControllerTest.java
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PostsApiControllerTest {
...
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
/* @Before
: 매번 테스트가 시작되기 전에 MockMvc 인스턴스를 생성 */
@Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
...
@Test
@WithMockUser(roles="USER")
public void Posts_등록된다() throws Exception {
...
// when
/* mvc.perform
: 생성된 MockMvc를 통해 API를 테스트
본문(Body) 영역은 문자열로 표현하기 위해 ObjectMapper를 통해 문자열 JSON으로 변환 */
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);
}
}
- 다시 gradle로 테스트를 수행해 보면 실패 테스트가 줄어든 것을 확인 가능
🌱 문제 3. @WebMvcTest에서 CustomOAuth2UserService를 찾을 수 없음
- "No qualifying bean of type 'com.gagyeong.book.springboot.config.auth.CustomOAuth2UserService'"
- 문제 1번과 다른 점은 @WebMvcTest를 사용한다는 점
- 문제 1번은 스프링 시큐리티 설정은 잘 작동했지만, @WebMvcTest는 CustomOAuth2UserService를 스캔하지 않기 때문
- @WebMvcTest는 WebSecurityConfigureAdapter, WebMvcConfigurer를 비롯한 @ControllerAdvice, @Controller는 스캔 대상이나, @Repository, @Service, @Component는 스캔 대상이 아님
- 그러니 SecurityConfig는 읽었지만, SecurityConfig를 생성하기 위해 필요한 CustomOAuth2UserService는 읽을 수가 없어 에러 발생
- 이를 해결하기 위해 스캔 대상에서 SecurityConfig를 제거해야 함
// test/web/HelloControllerTest.java
@WebMvcTest(controllers = HelloController.class,
excludeFilters = {
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = SecurityConfig.class)
}
)
- @WebMockUser를 사용해서 가짜로 인증된 사용자를 생성
// test/web/HelloControllerTest.java
@WithMockUser(roles = "USER")
@Test
public void hello가_리턴된다() throws Exception {
...
}
@WithMockUser(roles = "USER")
@Test
public void helloDto가_리턴된다() throws Exception {
...
}
- 다시 테스트를 돌려보면 추가 에러가 발생
- 이 에러는 @EnableJpaAuditing로 인해 발생하며 이를 사용하기 위해선 최소 하나의 @Entity 클래스가 필요하나, @WebMvcTest이다 보니 없음
- @EnableJpaAuditing가 @SpringBootApplication와 함께 있다보니 @WebMvcTest에서도 스캔하게 되어있음
- 그래서 @EnableJpaAuditing과 @SpringBootApplication를 둘로 분리
- Application.java에서 @EnableJpaAuditing를 제거함
// Application.java
// @EnableJpaAuditing가 삭제됨
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
- config 패키지에 JpaConfig를 생성하여 @EnableJpaAuditing를 추가
// main/java/config/auth/JpaConfig.java
@Configuration
@EnableJpaAuditing // JPA Auditing 활성화
public class JpaConfig {
}
- 다시 gradle로 테스트를 수행해 보면 모든 테스트를 통과

'Java-Spring > 스프링 부트와 AWS로 혼자 구현하는 웹 서비스' 카테고리의 다른 글
[Spring Boot] 07장. AWS에 데이터베이스 환경을 만들어보자 - AWS RDS (0) | 2021.11.06 |
---|---|
[Spring Boot] 06장. AWS 서버 환경을 만들어보자 - AWS EC2 (0) | 2021.11.01 |
[Spring Boot] 05장. 스프링 시큐리티와 OAuth 2.0으로 로그인 기능 구현하기 - 네이버 로그인 (0) | 2021.10.31 |
[Spring Boot] 05장. 스프링 시큐리티와 OAuth 2.0으로 로그인 기능 구현하기 - 세션 저장소로 데이터베이스 사용하기 (0) | 2021.10.30 |
[Spring Boot] 05장. 스프링 시큐리티와 OAuth 2.0으로 로그인 기능 구현하기 - 어노테이션 기반으로 개선하기 (0) | 2021.10.30 |
🌱 이번 장의 스터디 범위
- 스프링 시큐리티 적용 시 기존 테스트 코드에서 문제 해결 방법
🌱 기존 테스트에 시큐리티 적용으로 문제가 되는 부분 해결
- 기존에는 바로 API를 호출할 수 있어 테스트 코드 역시 바로 API를 호출하도록 구성하였음
- 하지만, 시큐리티 옵션이 활성화되면 인증된 사용자만 API를 호출할 수 있어
- 기존의 API 테스트 코드들이 모두 인증에 대한 권한을 받지 못하였으므로
- 테스트 코드마다 인증한 사용자가 호출한 것처럼 작동하도록 수정
- [Gradle 탭] - [Tasks] - [verification] - [test]를 선택해 전체 테스트를 수행

- 테스트 실행 시 롬복을 이용한 테스트 외에 스프링을 이용한 테스트는 모두 실패하는 것을 확인 가능

🌱 문제 1. CustomOAuth2UserService을 찾을 수 없음
- "No qualifying bean of type 'com.gagyeong.book.springboot.config.auth.CustomOAuth2UserService'"
- CustomOAuth2UserService를 생성하는데 필요한 소셜 로그인 관련 설정값들이 없기 때문에 발생
- application-oauth.properties에 설정값들을 추가했는데 왜 설정이 없다고 할까?
- 이는 src/ main 환경과 src/ test 환경의 차이 때문. 둘은 본인만의 환경 구성을 가짐
- 다만, src/ main/resources/application.properties가 테스트 코드를 수행할 때도 적용되는 이유는 test에 application.properties가 없으면 main의 설정을 그래도 가져오기 때문임.
- 다만, 자동으로 가져오는 옵션의 범위는 application.properties 파일까지이므로 application-oauth.properties는 test에 파일이 없다고 가져오는 파일이 아님
- 이를 해결하기 위해 테스트 환경을 위한 application.properties를 만들 것
- 실제로 구글 연동까지 진행할 것은 아니므로 가짜 설정값을 등록
// test/resources/application.properties
spring.jpa.show_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.h2.console.enabled=true
spring.session.store-type=jdbc
# Test OAuth
spring.security.oauth2.client.registration.google.client-id=test
spring.security.oauth2.client.registration.google.client-secret=test
spring.security.oauth2.client.registration.google.scope=profile,email
- 다시 gradle로 테스트를 수행해 보면 실패 테스트가 줄어든 것을 확인 가능
🌱 문제 2. 302 Status Code
- 응답의 결과로 200 (정상 응답) status code를 원했지만 결과는 302 (리다이렉션 응답) status code가 와서 실패

- 이는 스프링 시큐리티 설정 때문에 인증되지 않은 사용자의 요청은 이동시키기 때문임
- 이를 해결하기 위해 이런 API 요청은 임의의 인증된 사용자를 추가하여 API만 테스트해 볼 수 있도록 함
- 스프링 시큐리티 테스트를 위한 여러 도구를 지원하는 spring-security-test를 build.gradle에 추가
// build.gradle
testCompile('org.springframework.security:spring-security-test')
- PostsApiControllerTest의 2개 테스트 메소드에 임의의 사용자 인증을 추가
// test/web/PostsApiController.java
@Test
/* @WithMockUser(roles="USER")
: 인증된 모의 (가짜) 사용자를 만들어서 사용
roles에 권한을 추가할 수 있음
즉, 어노테이션으로 인해 ROLE_USER 권한을 가진 사용자가 API를 요청하는 것과 동일한 효과 */
@WithMockUser(roles="USER")
public void Posts_등록된다() throws Exception {
...
}
@Test
@WithMockUser(roles="USER")
public void Posts_수정된다() throws Exception {
...
}
- @WithMockUser가 MockMvc에서만 작동하기 때문에 PostsApiController에 MockMvc를 사용하도록 해야 함
- @SpringBootTest에서 MockMvc를 사용하는 방법 코드로 변경
// test/web/PostsApiControllerTest.java
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PostsApiControllerTest {
...
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
/* @Before
: 매번 테스트가 시작되기 전에 MockMvc 인스턴스를 생성 */
@Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
...
@Test
@WithMockUser(roles="USER")
public void Posts_등록된다() throws Exception {
...
// when
/* mvc.perform
: 생성된 MockMvc를 통해 API를 테스트
본문(Body) 영역은 문자열로 표현하기 위해 ObjectMapper를 통해 문자열 JSON으로 변환 */
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);
}
}
- 다시 gradle로 테스트를 수행해 보면 실패 테스트가 줄어든 것을 확인 가능
🌱 문제 3. @WebMvcTest에서 CustomOAuth2UserService를 찾을 수 없음
- "No qualifying bean of type 'com.gagyeong.book.springboot.config.auth.CustomOAuth2UserService'"
- 문제 1번과 다른 점은 @WebMvcTest를 사용한다는 점
- 문제 1번은 스프링 시큐리티 설정은 잘 작동했지만, @WebMvcTest는 CustomOAuth2UserService를 스캔하지 않기 때문
- @WebMvcTest는 WebSecurityConfigureAdapter, WebMvcConfigurer를 비롯한 @ControllerAdvice, @Controller는 스캔 대상이나, @Repository, @Service, @Component는 스캔 대상이 아님
- 그러니 SecurityConfig는 읽었지만, SecurityConfig를 생성하기 위해 필요한 CustomOAuth2UserService는 읽을 수가 없어 에러 발생
- 이를 해결하기 위해 스캔 대상에서 SecurityConfig를 제거해야 함
// test/web/HelloControllerTest.java
@WebMvcTest(controllers = HelloController.class,
excludeFilters = {
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = SecurityConfig.class)
}
)
- @WebMockUser를 사용해서 가짜로 인증된 사용자를 생성
// test/web/HelloControllerTest.java
@WithMockUser(roles = "USER")
@Test
public void hello가_리턴된다() throws Exception {
...
}
@WithMockUser(roles = "USER")
@Test
public void helloDto가_리턴된다() throws Exception {
...
}
- 다시 테스트를 돌려보면 추가 에러가 발생
- 이 에러는 @EnableJpaAuditing로 인해 발생하며 이를 사용하기 위해선 최소 하나의 @Entity 클래스가 필요하나, @WebMvcTest이다 보니 없음
- @EnableJpaAuditing가 @SpringBootApplication와 함께 있다보니 @WebMvcTest에서도 스캔하게 되어있음
- 그래서 @EnableJpaAuditing과 @SpringBootApplication를 둘로 분리
- Application.java에서 @EnableJpaAuditing를 제거함
// Application.java
// @EnableJpaAuditing가 삭제됨
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
- config 패키지에 JpaConfig를 생성하여 @EnableJpaAuditing를 추가
// main/java/config/auth/JpaConfig.java
@Configuration
@EnableJpaAuditing // JPA Auditing 활성화
public class JpaConfig {
}
- 다시 gradle로 테스트를 수행해 보면 모든 테스트를 통과

'Java-Spring > 스프링 부트와 AWS로 혼자 구현하는 웹 서비스' 카테고리의 다른 글
[Spring Boot] 07장. AWS에 데이터베이스 환경을 만들어보자 - AWS RDS (0) | 2021.11.06 |
---|---|
[Spring Boot] 06장. AWS 서버 환경을 만들어보자 - AWS EC2 (0) | 2021.11.01 |
[Spring Boot] 05장. 스프링 시큐리티와 OAuth 2.0으로 로그인 기능 구현하기 - 네이버 로그인 (0) | 2021.10.31 |
[Spring Boot] 05장. 스프링 시큐리티와 OAuth 2.0으로 로그인 기능 구현하기 - 세션 저장소로 데이터베이스 사용하기 (0) | 2021.10.30 |
[Spring Boot] 05장. 스프링 시큐리티와 OAuth 2.0으로 로그인 기능 구현하기 - 어노테이션 기반으로 개선하기 (0) | 2021.10.30 |