[Spring] WebTestClient 사용기

민스킴·2024년 8월 27일
0

Spring

목록 보기
10/12

WebTestClient 사용해보자

Cloud Native Spring in Action 책을 공부하다가 WebFlux 프로젝트의 일부인 WebTestClient를 알게 되었다.

모의 환경(Mock)과 실행 서버 환경(HTTP)을 모두 테스트 해볼 수 있으며, 현대적이며 풍부한 API를 제공하고 명령형 애플리케이션뿐 아니라 반응형 애플리케이션도 테스트 해볼 수 있는 최신 도구라고 했다.

새로운 테스트 도구를 알게 되어서 기쁜 마음으로 테스트 코드를 작성하였고, 곧 바로 실행해보았다.
아래에 코드를 바로 보자.


GradeController.java

@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 등의 클래스 구현은 몰라도 상관없기에 생략한다.

GradeControllerTest.java

@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 응답을 뱉어서 매우 당황스러웠다. 이유가 뭘까 ?

문제1 - CSRF

고맙다 ChatGPT

REST API 서버로 개발하지 않기 때문에 CSRF 설정을 비활성화 하지 않았던 것을 잊고 있었다.
그렇다면 그냥 비활성화하고 끝낼 수 있을까? 그렇지 않다. 이유는 여기를 참고

문제2 - httpHandlerBuilder is null

테스트 코드에 CSRF 토큰과 Mock 인증 객체를 추가하는 코드를 넣었다. 프로젝트가 OAuth2 로그인으로 구현되어 있기 때문에 mockOAuth2Login()을 사용했다.

GradeControllerTest.java


@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();
                });
    }
}

문제3 - 의존성 주입 실패

이제 슬슬 성공 할 때도 됐는데 계속 실패하였다.
실패 로그를 보니 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 설정

profile
Boys, be ambitious!

0개의 댓글