Cloud Native Spring in Action
책을 공부하다가 WebFlux 프로젝트의 일부인 WebTestClient를 알게 되었다.
모의 환경(Mock)과 실행 서버 환경(HTTP)을 모두 테스트 해볼 수 있으며, 현대적이며 풍부한 API를 제공하고 명령형 애플리케이션뿐 아니라 반응형 애플리케이션도 테스트 해볼 수 있는 최신 도구라고 했다.
새로운 테스트 도구를 알게 되어서 기쁜 마음으로 테스트 코드를 작성하였고, 곧 바로 실행해보았다.
아래에 코드를 바로 보자.
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/grade")
public class GradeController {
private final GradeService gradeService;
// 과제 테스트 실행
@PostMapping
public JsonBody<GradeResponse> testGrade(@AuthenticationPrincipal CustomOAuth2User oAuth2User, @RequestBody GradeRequest gradeRequest) {
GradeResponse gradeResponse = gradeService.testGrade(oAuth2User.getMemberId(), gradeRequest);
return JsonBody.success(GRADE_SUCCESS, gradeResponse);
}
CustomOAuth2User, GradeRequest 등의 클래스 구현은 몰라도 상관없기에 생략한다.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@DisplayName("GradeController 테스트")
public class GradeControllerTest {
@Autowired
private WebTestClient webTestClient;
@Test
@DisplayName("과제 테스트 실행 - 실패")
void testGrade() {
GradeRequest gradeRequest = new GradeRequest();
gradeRequest.setSolutionId(1L);
gradeRequest.setTestCaseId(1L);
gradeRequest.setCode("code");
webTestClient
.post()
.uri("/api/grade")
.bodyValue(gradeRequest)
.exchange()
.expectStatus().isOk()
.expectBody(JsonBody.class).value(jsonBody -> {
assertThat(jsonBody.getStatus()).isEqualTo(SuccessStatus.GRADE_SUCCESS.getStatus());
assertThat(jsonBody.getMessage()).isEqualTo(SuccessStatus.GRADE_SUCCESS.getMessage());
assertThat(jsonBody.getData()).isNotNull();
});
}
}
한번에 성공했다면 참 좋았을텐데, 세상은 호락호락하지 않다.
결코 한번에 동작해주지 않는다. 바로 실패 로그를 뱉었따.
나는 모든 사용자가 접근 할 수 있는 API를 테스트 하는 것인데 403
응답을 뱉어서 매우 당황스러웠다. 이유가 뭘까 ?
고맙다 ChatGPT
REST API 서버로 개발하지 않기 때문에 CSRF 설정을 비활성화 하지 않았던 것을 잊고 있었다.
그렇다면 그냥 비활성화하고 끝낼 수 있을까? 그렇지 않다. 이유는 여기를 참고
테스트 코드에 CSRF 토큰과 Mock 인증 객체를 추가하는 코드를 넣었다. 프로젝트가 OAuth2 로그인으로 구현되어 있기 때문에 mockOAuth2Login()
을 사용했다.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@DisplayName("GradeController 테스트")
public class GradeControllerTest {
@Autowired
private WebTestClient webTestClient;
@Test
@DisplayName("과제 테스트 실행 - 실패")
void testGrade() {
GradeRequest gradeRequest = new GradeRequest();
gradeRequest.setSolutionId(1L);
gradeRequest.setTestCaseId(1L);
gradeRequest.setCode("code");
webTestClient
// 여기부터 =========================================================
.mutateWith(csrf())
.mutateWith(mockOAuth2Login().oauth2User(MockKakaoUser()))
// 여기까지 추가함 ===================================================
.post()
.uri("/api/grade/test")
.bodyValue(gradeRequest)
.exchange()
.expectStatus().isOk()
.expectBody(JsonBody.class).value(jsonBody -> {
assertThat(jsonBody.getStatus()).isEqualTo(SuccessStatus.GRADE_SUCCESS.getStatus());
assertThat(jsonBody.getMessage()).isEqualTo(SuccessStatus.GRADE_SUCCESS.getMessage());
assertThat(jsonBody.getData()).isNotNull();
});
}
}
httpHandlerBuilder가 null이기 때문에 발생하는 에러라고 한다. 한참동안 해결 방법을 찾았고, 그 방법은 아래와 같다.
@WebFluxTest 써라
WebTestClient 관련 설정을 적용하기 위해서는 @WebFluxTest 어노테이션을 사용하는 방법밖에 없었다.
아래와 같이 코드를 수정하였다.
@WebFluxTest(controllers = GradeController.class)
@DisplayName("GradeController 테스트")
public class GradeControllerTest {
@Autowired
private WebTestClient webTestClient;
@Test
@DisplayName("과제 테스트 실행 - 실패")
void testGrade() {
GradeRequest gradeRequest = new GradeRequest();
gradeRequest.setSolutionId(1L);
gradeRequest.setTestCaseId(1L);
gradeRequest.setCode("code");
webTestClient
.mutateWith(csrf())
.mutateWith(mockOAuth2Login().oauth2User(MockKakaoUser()))
.post()
.uri("/api/grade")
.bodyValue(gradeRequest)
.exchange()
.expectStatus().isOk()
.expectBody(JsonBody.class).value(jsonBody -> {
assertThat(jsonBody.getStatus()).isEqualTo(SuccessStatus.GRADE_SUCCESS.getStatus());
assertThat(jsonBody.getMessage()).isEqualTo(SuccessStatus.GRADE_SUCCESS.getMessage());
assertThat(jsonBody.getData()).isNotNull();
});
}
}
이제 슬슬 성공 할 때도 됐는데 계속 실패하였다.
실패 로그를 보니 GradeController에서 의존하고 있는 GradeService 주입에 실패하였다는 소리였다.
이유를 찾아보니 @WebFluxTest는 슬라이스 테스트기 때문에 모든 컴포넌트들을 빈으로 등록하지 않는다고 한다.
@WebFluxTest
는 Spring WebFlux 인프라를 자동으로 구성하고 빈을 스캔합니다.@Controller
,@ControllerAdvice
,@JsonComponent
,Converter
,GenericConverter
,WebFluxConfigurer
로 제한합니다.@WebFluxTest
어노테이션을 사용하면 일반@Component
빈은 검색되지 않습니다.
라고 하네요... GradeService 빈을 직접 생성하고 주입하도록 코드를 고쳤습니다.
@WebFluxTest(controllers = GradeController.class)
@DisplayName("GradeController 테스트")
public class GradeControllerTest {
@Autowired
private WebTestClient webTestClient;
@TestConfiguration
static class Config {
@Bean
public GradeService gradeService() {
return new GradeService(null);
}
}
@Test
@DisplayName("과제 테스트 실행 - 실패")
void testGrade() {
GradeRequest gradeRequest = new GradeRequest();
gradeRequest.setSolutionId(1L);
gradeRequest.setTestCaseId(1L);
gradeRequest.setCode("code");
webTestClient
.mutateWith(csrf())
.mutateWith(mockOAuth2Login().oauth2User(MockKakaoUser()))
.post()
.uri("/api/grade")
.bodyValue(gradeRequest)
.exchange()
.expectStatus().isOk()
.expectBody(JsonBody.class).value(jsonBody -> {
assertThat(jsonBody.getStatus()).isEqualTo(SuccessStatus.GRADE_SUCCESS.getStatus());
assertThat(jsonBody.getMessage()).isEqualTo(SuccessStatus.GRADE_SUCCESS.getMessage());
assertThat(jsonBody.getData()).isNotNull();
});
}
}
테스트 실행에 성공했습니다. 단순히 새로 배운 테스트 코드 툴을 사용해보려 했을 뿐인데 매우 오랜 시간이 걸렸습니다.
이번에 고생한 만큼 다음번에는 고생하지 않고 빠르게 적용해서 테스트 코드를 잘 작성할 수 있을 것 같습니다.
감사합니다.
WebTestClient 가이드
@SpringBootTest와 함께 사용시 문제점
CSRF 설명1
CSRF 설명2
반응형 애플리케이션과 WebFlux
WebFluxTest 설정