아직 RestDocs는 만들지 않은 상태로 회원가입 성공 컨트롤러 테스트를 작성하였다. 코드는 아래
@WebMvcTest(value = MemberController.class,
excludeFilters = {
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = SecurityConfig.class),
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = JwtAuthenticationFilter.class)
}
)
@AutoConfigureRestDocs
class MemberControllerTest {
@MockBean
private MemberServiceImpl memberService;
@Autowired
private MockMvc mockMvc;
@Autowired
private WebApplicationContext webApplicationContext;
@Autowired
private ObjectMapper objectMapper;
@BeforeEach
void setUp() {
this.mockMvc = MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.apply(springSecurity())
.defaultRequest(post("/**").with(csrf()))
.defaultRequest(put("/**").with(csrf()))
.defaultRequest(delete("/**").with(csrf()))
.build();
}
@Test
@DisplayName("회원가입 성공 테스트")
void singUpSuccessTest() throws Exception {
//given
Signup.Request request =
Signup.Request.builder()
.name("소은")
.phone("010-1111-1111")
.email("soni@naver.com")
.password("qwerty")
.address("서울특별시 강남구")
.birthDay("2000-01-28")
.build();
given(memberService.signUp(request))
.willReturn(new Signup.Response(
"소은", "님 회원가입이 완료되었습니다."));
//when
//then
mockMvc.perform(post("/member/signup")
// .header("Authorization", "Bearer AccessToken")
.content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("소은"));
}
}
그런데 다음과 같은 오류가 나왔다.
대체 왜 ?????
SecurityConfig에서도 이렇게 해당 URL에 대해서는 모든 권한을 허용하도록 해놨다.
.antMatchers("/member/signup", "/member/signin", "/oauth/**").permitAll()
따라서 postman에서 요청할 때도 AccessToken을 넣지 않고 정상적으로 동작한다.
로직이 로그인할 때 AccessToken이 발급되므로 회원가입하는 사람은 Token을 가지고 있을 수가 없는 구조이다.
그래서 전에 만들어놨던 @WithMockUser 없이 동작을 해야한다 !
excludeFilters로 SecurityConfig를 회피하도록도 해놓았는데 왜 안 될까 ?
정말 이해할 수가 없군....... 이전 FriendController 테스트 할 때는 이 방식으로 해서 잘 동작했는데 말이다.
시도한 방법들
1. excludeFilter 로 SecurityConfig.class와 JwtAuthenticationFilter.class를 거치지 않도록 해주었다.
-> 해결되지 않음
2. @WebMvcTest에서 Spring-security-test 가 클래스 경로에 있으면 스프링 보안을 자동으로 구성하기 때문에 내 설정이 아닌 자동으로 구성된 설정이 적용된다고 한다.
-> @ContextConfiguration(classes = {SecurityConfig.class})를 해주어 내가 설정한 Security 옵션을 적용하도록 하였다.
-> 그래도 해결되지 않음.
3. Security자동 설정을 막기 위해 excludeAutoConfiguration = SecurityAutoConfiguration.class을 추가해주었다.
-> 해결 ! - 아예 Controller 테스트 시 SpringSecurity 설정을 제외 하는 방법.
=> 역시 결론은 Spring Security의 자동 설정이 적용되어 그런 것 같다. 이렇게 하면 csrf() 설정을 해주지 않아도 테스트가 잘 통과한다.
@WebMvcTest(controllers = MemberController.class,
excludeAutoConfiguration = SecurityAutoConfiguration.class,
excludeFilters = {
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = SecurityConfig.class),
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = JwtAuthenticationFilter.class)
}
)
4. 테스트용 예외 설정을 따로 추가하는 방법
아래처럼 테스트 코드 내에 테스트용 설정 클래스를 만들어주었다.
@TestConfiguration
static class DefaultConfigWithoutCsrf extends WebSecurityConfigurerAdapter {
// @Override
// protected void configure(final HttpSecurity http) throws Exception {
// super.configure(http);
// http.csrf().disable();
// }
public void configure(WebSecurity web) throws Exception {
web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
web.ignoring().antMatchers("/member/signup"); // 테스트용 API의 Security 비활성화
}
}
주석처리한 부분은 csrf를 disable() 하는 부분인데 없어도 테스트가 잘 통과한다.
3번이나 4번으로 해결하면 된다 ! ~.~
+
MemberController에서 회원가입, 로그인은 @WithMockUser라든지 가짜 사용자 객체가 필요없지만
userInfo, update 메소드는 로그인한 사용자를 기준으로 정보를 보여주거나 정보를 수정하기 때문에 필요하다.
@Test
@DisplayName("본인 정보 조회 성공 테스트")
void userInfoSuccessTest() throws Exception {
//given
Product product1 = new Product(1L, "반지", 10000L, 1);
Product product2 = new Product(2L, "목걸이", 20000L, 2);
List fundingProductDtoList = Arrays.asList(
FundingProductDto.builder()
.id(1L)
.product(product1)
.total(5000L)
.build(),
FundingProductDto.builder()
.id(2L)
.product(product2)
.total(1000L)
.build()
);
given(memberService.userInfo(any()))
.willReturn(new UserInfoResponse(
"소은",
"010-1111-1111",
"soni@naver.com",
"서울특별시 강남구",
LocalDate.of(2000, 01, 28),
new ArrayList<>(fundingProductDtoList)
));
//when
//then
mockMvc.perform(get("/member/info"))
.andDo(print())
.andExpect(status().isOk());
}
여기서 값을 given절에 any()로 넣었기 떄문에 어떤 요청이 들어가든지 mocking 해놓은 결과값이 나오게 된다.
@WithMockUser를 해주지 않아도 테스트가 통과한다.
어떻게 할지 생각을 해봤다.
1. Security 설정을 넣고 로그인이 필요없는 API 테스트에도 @WithMockUser를 붙여주기
위에서 설정해줬던
를 없애면 csrf() 에러가 나는데
일일히 .with(csrf()) 해주기가 귀찮아서 @TestConfiguration 클래스를 만들었다.
@TestConfiguration
static class DefaultConfigWithoutCsrf extends WebSecurityConfigurerAdapter {
@Override
protected void configure(final HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().disable();
}
}
이렇게 할 경우에는 원래 WithMockUser가 필요없던 회원가입, 로그인에도 @WithMockUser를 넣어줘야 함.
2. 기존 해놨던 것처럼 Security 설정 무시하고 그냥 @WithMockUser 필요한 API에도 안 붙여주기
뭐 이렇게 해도 security 인증과정을 거치지 않으니 401, 403 에러가 나오지 않고 given절에 any()로 하기 때문에 응답은 Mocking한 그대로 나온다.
처음엔 1로 바꾸려고 했는데 생각해보니까 @WithMockUser를 넣어준다고 any()를 안 쓰는 건 아니라 어차피 @WithMockUser에 사용자 정보로 뽑아올 수 있는 건 아니라 ,,, 2번으로 하기로 했다.
엥 근데 왜 any()해줬드라 ?!
아 맞다 내가 Mocking 해준 Member 객체와 실제 요청에 들어가는 Member 객체가 달라서 오류가 나서 그랬다.. . .
이경우 any() 로 해주고 jsonPath에 대한 추가 검증을 하거나 equals & hashcode를 구현해주면 된다고 했는데 ,.,.,
아 any()로 하는 거 정말 정말 맘에 안 든다
참고
https://jiwondev.tistory.com/270
https://yeongchan1228.tistory.com/88
'프로젝트' 카테고리의 다른 글
GiftFunding) Redisson을 이용한 동시성 이슈 제어 (0) | 2024.05.30 |
---|---|
GitHub Actions vs Jenkins (0) | 2024.04.23 |
GiftFunding) Service 테스트 코드 작성하기 (0) | 2024.04.17 |
GiftFunding) RestDocs + Swagger 적용하기(feat. Controller 테스트 코드 작성) (0) | 2024.04.13 |
GiftFunding) 인증이 필요한 컨트롤러 메소드에 대해 @WithMockUser로 테스트 (1) | 2024.04.08 |