ResultActions

appti·2023년 5월 21일
1

학습 로그

목록 보기
7/8

ResultActions

MockMvc를 활용해 Controller에 대한 슬라이스 테스트를 작성한다면, 주로 다음과 같이 작성할 것입니다.

mockMvc.perform(post("/lines/{lineId}/sections", 1L)
        .contentType(MediaType.APPLICATION_JSON)
        .content(objectMapper.writeValueAsString(request)))
        .andExpectAll(
                status().isCreated(),
                jsonPath("$.stationResponses[0].id", is(upStation.getId()), Long.class),
                jsonPath("$.stationResponses[0].name", is(upStation.getName())),
                jsonPath("$.stationResponses[1].id", is(downStation.getId()), Long.class),
                jsonPath("$.stationResponses[1].name", is(downStation.getName()))
        );

이 때, 검증에 사용되는 ResultActions(andExpect(), andExpectAll())에 대해 간단히 살펴보고자 합니다.

ResultActions

문서에서 ResultActions는 다음과 같이 설명하고 있습니다.

실행된 요청에 대해, 기대하고 있는 결과 혹은 동작을 적용할 수 있습니다.

mockMvc를 통해 요청을 수행한 결과를 관리하는 클래스임을 알 수 있습니다.
ResultActions에서 제공해주는 기능을 통해, 요청의 결과를 활용할 수 있다고 이해할 수 있겠습니다.

andExpect()

기대하고 있는 내용을 수행할 수 있는 메소드입니다.

해당 메소드의 가장 큰 특징은 자기 자신인 ResultActions를 반환한다는 점입니다.

이를 통해 다음과 같은 Chaining Pattern을 적용할 수 있습니다.

mockMvc.perform(post("/lines/{lineId}/sections", 1L)
        .contentType(MediaType.APPLICATION_JSON)
        .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isCreated())
        .andExpect(jsonPath("$.stationResponses[0].id", is(upStation.getId()), Long.class))
        .andExpect(jsonPath("$.stationResponses[0].name", is(upStation.getName())))
        .andExpect(jsonPath("$.stationResponses[1].id", is(downStation.getId()), Long.class))
        .andExpect(jsonPath("$.stationResponses[1].name", is(downStation.getName())));

다만, 주의할 점이 하나 필요합니다.

mockMvc.perform(post("/lines/{lineId}/sections", 1L)
        .contentType(MediaType.APPLICATION_JSON)
        .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isBadGateway())
        .andExpect(jsonPath("$.stationResponses[0].id", is(-999L), Long.class))
        .andExpect(jsonPath("$.stationResponses[0].name", is("wrong name")))
        .andExpect(jsonPath("$.stationResponses[1].id", is(-999L), Long.class))
        .andExpect(jsonPath("$.stationResponses[1].name", is("wrong name")));

위와 같이, andExpect()로 지정한 모든 조건들이 false가 나오도록 변경했습니다.

테스트 결과는 어떤 식으로 나올까요?

5가지 테스트가 모두 틀렸지만, 상태 코드, status()에 대한 값만이 검증되는 것을 확인할 수 있습니다.

즉, assertThat()의 문제를 그대로 가지고 있다고 볼 수 있습니다.

andExpectAll()

이럴 때 사용할 수 있는 것이 andExpectAll() 메소드 입니다.

문서에서도 This feature is similar to the SoftAssertions support in AssertJ and the assertAll() support in JUnit Jupiter., 즉 assertAll()과 유사하다고 적혀 있습니다.

mockMvc.perform(post("/lines/{lineId}/sections", 1L)
        .contentType(MediaType.APPLICATION_JSON)
        .content(objectMapper.writeValueAsString(request)))
        .andExpectAll(
                status().isBadGateway(),
                jsonPath("$.stationResponses[0].id", is(-999L), Long.class),
                jsonPath("$.stationResponses[0].name", is("wrong name")),
                jsonPath("$.stationResponses[1].id", is(-999L), Long.class),
                jsonPath("$.stationResponses[1].name", is("wrong name"))
        );

이전과 동일하게 5개의 항목 모두 실패하는 테스트를 작성하고 실행하면 다음과 같이 동작함을 확인할 수 있습니다.

ResultMatcher

andExpect(), andExpectAll() 모두 ResultMatcher를 파라미터로 받는 것을 확인하셨을 겁니다.

ResultMatcher는 인터페이스로, 요청에 의한 결과가 지정한 예상 값과 일치(match)하는지 확인하는 용도입니다.

match() 메소드를 구현하면 되며, 일치하지 않을 시 Exception을 던지도록 되어 있습니다.

다만 우리는 이를 직접 구현할 필요가 없습니다.
스프링이 MockMvcResultMatchers라는 이름의 Factory Class를 제공해주기 때문입니다.

MockMvcResultMatchers

설명부터 ResultMatcher 기반의 Factory Method를 지원한다는 내용을 확인할 수 있습니다.

코드를 직접 확인해보면, 다음과 같은 내용을 확인할 수 있습니다.
그 항목을 간략하게 정리하면 다음과 같습니다.

메소드설명
content()응답 본문의 내용을 검증합니다.
header()응답 헤더의 내용을 검증합니다.
status()응답 상태 코드를 검증합니다.
jsonPath()JSON 응답 본문의 특정 부분을 검증합니다.
xpath()XML 응답 본문의 특정 부분을 검증합니다.
view()선택된 뷰를 검증합니다.
flash()플래시 속성을 검증합니다.
forwardedUrl()요청이 전달된 URL을 검증합니다.
redirectedUrl()요청이 리디렉션된 URL을 검증합니다.
model()모델 속성을 검증합니다.
handler()요청을 처리한 핸들러를 검증합니다.

다양한 메소드를 제공해주는 것을 확인할 수 있는데, 이 중 자주 사용했던 status()jsonPath()를 확인해보도록 하겠습니다.

StatusResultMatchers

StatusResultMatchersstatus()를 호출하면 반환하는 값으로, HTTP 상태 코드를 검증하는데 사용합니다.

특이 사항으로는, 다양한 HTTP 상태 코드를 검증하는 메소드를 제공해준다는 점입니다.

그 항목을 간략하게 정리하면 다음과 같습니다.

메소드설명
is1xxInformational()1xx 범위의 응답 상태 코드가 있는지 확인합니다.
is2xxSuccessful()2xx 범위의 응답 상태 코드가 있는지 확인합니다.
is3xxRedirection()3xx 범위의 응답 상태 코드가 있는지 확인합니다.
is4xxClientError()4xx 범위의 응답 상태 코드가 있는지 확인합니다.
is5xxServerError()5xx 범위의 응답 상태 코드가 있는지 확인합니다.
is(int status)지정된 정수 값과 동일한 응답 상태 코드가 있는지 확인합니다.
is(HttpStatus status)지정된 HTTP 상태 코드와 동일한 응답 상태 코드가 있는지 확인합니다.
isOk()200 OK 상태 코드가 있는지 확인합니다.
isCreated()201 Created 상태 코드가 있는지 확인합니다.
isNoContent()204 No Content 상태 코드가 있는지 확인합니다.
isNotFound()404 Not Found 상태 코드가 있는지 확인합니다.
isMethodNotAllowed()405 Method Not Allowed 상태 코드가 있는지 확인합니다.
isInternalServerError()500 Internal Server Error 상태 코드가 있는지 확인합니다.

상태 코드의 범위를 확인할 수도 있고, 특정 상태 코드를 명시할 수도 있으니 적절하게 사용하면 될 것 같습니다.

JsonPathResultMatchers

JsonPathResultMatchersjsonPath()를 호출하면 반환하는 값으로, 표현식을 통해 응답에 접근하고 검증할 수 있습니다.

표현식

json 형식에 접근하기 위한 수단입니다.
다음과 같은 기능을 통해 다양한 방식으로 json에 접근할 수 있습니다.

글자설명
$현재 노드를 의미합니다.
*모든 자식 필드에 대해 접근합니다.
.자식 필드에 대해 접근합니다.
[]배열 요소에 대해 접근합니다.
@속성에 대해 접근합니다.

예를 들어, 다음과 같이 사용할 수 있습니다.

// users라는 배열 중 첫 번째 인덱스의 name 속성
jsonPath("$.users[0].name") 

// user라는 객체의 name 속성
jsonPath("$.user.@name")

형 변환

jsonPath()는 일반적으로 사용하는 jsonPath(String, Matcher) 클래스와,

오버로딩이 적용된 jsonPath(String, Matcher, Class)가 있습니다.

문서를 확인해보면, This can be useful for matching numbers reliably — for example, to coerce an integer into a double., 이 기능은 Integer를 Double로 강제 캐스팅하는 등 숫자를 안정적으로 일치시키는 데 유용할 수 있습니다.라고 설명하고 있습니다.

내부 코드를 확인해보면, 검증에 실패하는 경우를 지정한 값이 다를 경우 뿐만 아니라, 지정한 타입으로 캐스팅하지 못한 경우에도 AssertionError, 검증이 실패함을 확인할 수 있습니다.

즉 잘못된 타입 캐스팅을 할 경우에 별도의 예외 처리가 필요 없다는 것입니다.

예시를 통해 확인해보겠습니다.

mockMvc.perform(post("/lines")
        .contentType(MediaType.APPLICATION_JSON)
        .content(objectMapper.writeValueAsString(request)))
        .andExpectAll(
                status().isCreated(),
                jsonPath("$.id", is(line.getId())),
                jsonPath("$.name", is(line.getName())),
                jsonPath("$.color", is(line.getColor()))
        );

이 테스트는 다음과 같이 실패합니다.

이를 성공시키기 위해서는, jsonPath()의 세 번째 인자를 통해 타입을 강제로 캐스팅해줄 수 있습니다.

mockMvc.perform(post("/lines")
        .contentType(MediaType.APPLICATION_JSON)
        .content(objectMapper.writeValueAsString(request)))
        .andExpectAll(
                status().isCreated(),
                jsonPath("$.id", is(line.getId()), Long.class),
                jsonPath("$.name", is(line.getName())),
                jsonPath("$.color", is(line.getColor()))
        );
profile
안녕하세요

0개의 댓글