REST-assured 알아보기

초코칩·2024년 4월 19일
0

Test

목록 보기
3/4
post-thumbnail

REST-assured

REST-assured는 Java용 API 테스트 프레임워크로, RESTful 웹 서비스의 자동화된 테스트를 지원한다. 이를 사용하면 HTTP 요청을 보내고 응답을 검증하여 RESTful 웹 서비스의 기능을 테스트할 수 있다.

의존성 설정

의존성은 rest-assured와 hamcrest를 추가한다.

testImplementation 'org.hamcrest:hamcrest:2.1'
testImplementation 'io.rest-assured:rest-assured:5.3.1'

아래와 같이 spring-boot-starter-test를 설정해도 hamcrest를 사용할 수 있다.

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured:5.3.1'

MVN Repository에서 Hamcrest가 포함되어 있다는 설명을 볼 수 있다.

활용

시작하기 전에, 다음과 같이 import를 설정합니다.

import static org.hamcrest.Matchers.is;

import io.restassured.RestAssured;
import io.restassured.http.ContentType;

기본

아래 JSON 응답은 로컬에 배포된 API인 GET http://localhost:8080/reservations/1에 요청한 결과 응답이다.

{
  "id": 1,
  "name": "초코칩",
  "date": "2024-04-19",
  "time": "15:30:00"
}

REST-assured를 사용하여 응답 JSON을 검증해 보자.

@DisplayName("예약 조회에 성공하다.")
@Test
public void getReservation() {
        RestAssured.get("/reservations/1")
                .then()
                .statusCode(200)
                .body("name", equalTo("초코칩"));
}

위 코드에서 우리가 한 것은 /reservations/1 엔드포인트를 호출할 때 데이터 개체의 name이 "초코칩"인 JSON 문자열이 포함된 본문으로 응답하는지 확인한 것이다.

익명 JSON

객체(object) 대신 기본(primitive)으로 이루어진 배열을 응답으로 받는다고 가정하자.

[1, 2, 3]

이것을 key-value 쌍이 없는 익명 JSON 루트라고 한다. 데이터를 Collection 형태로 응답하면 구현 가능하기 때문에 유효한 JSON 데이터다.

이런 경우에는 $ 기호나 빈 문자열("")을 경로로 사용하여 검증할 수 있다.

위 서비스를 http://localhost:8080/json을 통해 노출한다고 가정해보자. 그렇다면 REST-assured를 사용하여 다음과 같이 검증할 수 있다.

when().get("/json").then().body("$", hasItems(1, 2, 3));

또는 다음과 같이 할 수 있다.

when().get("/json").then().body("", hasItems(1, 2, 3));

Syntactic Sugar

Syntactic sugar는 코드를 더 읽기 쉽고 간결하게 만들기 위해 도입된 문법적 요소를 가리킨다. 이러한 구문적 설탕은 코드의 가독성을 높이고 작성하는 데 필요한 노력을 줄이는 데 도움이 된다. 기능적으로 영향을 주지 않는다.

when()

when() 메서드를 사용하여 테스트의 전체적인 흐름을 의미적으로 더 명확하게 나타낼 수 있다. 이는 BDD(Behavior-Driven Development) 스타일을 지원하며, 테스트의 의도를 보다 명확하게 전달할 수 있다.

RestAssured.given()
			.contentType(ContentType.JSON)
			.body(params)
			.post("/reservations")
			.then().log().all()
			.statusCode(200)
			.body("name", is("브라운"));
RestAssured
		.given()
			.contentType(ContentType.JSON)
			.body(params)
		.when()
        	.post("/reservations")
		.then()
        	.log().all()
			.statusCode(200)
			.body("name", is("브라운"));

and()

and() 메서드도 REST Assured에서의 Syntactic sugar이다.

and() 메서드는 여러 가지 단언문(assertion)을 결합하거나 여러 가지 테스트 조건을 연결할 때 사용된다. 이를 통해 코드를 읽기 쉽고 명확하게 만들어 준다.

예를 들어, 여러 개의 단언문을 동시에 적용하고 싶을 때 and() 메서드를 사용할 수 있다.

given()
    .contentType(ContentType.JSON)
    .body("{ \"name\": \"John\" }")
.when()
    .post("/users")
.then()
    .statusCode(201)
.and()
    .body("name", equalTo("John"));

위 예제에서 and() 메서드는 단언문 .statusCode(201).body("name", equalTo("John"))을 연결하여 동시에 적용하도록 한다. 이렇게 하면 테스트 코드가 더 읽기 쉽고 이해하기 쉬워진다.

Float, Double

REST 서비스를 테스트하기 위해 REST-assured를 사용할 때, JSON 응답에서의 부동 소수점 숫자는 기본 float 유형으로 매핑되어야 한다.

float 유형의 사용은 Java의 많은 시나리오에서 double과 상호 교환 가능한 것과는 달리 그렇지 않습니다.

예를 들어, 다음과 같은 응답이 있습니다:

{
    "odd": {
        "price": "1.30",
        "ck": 12.2,
        "name": "1"
    }
}

ck 값에 대해 다음과 같은 테스트를 실행한다고 가정해보자.

get("/odd").then().assertThat().body("odd.ck", equalTo(12.2));

이 테스트는 우리가 테스트하는 값이 응답 값과 같더라도 실패할 것이다. 이는 double 대신 float와 비교하고 있기 때문이다.

이것을 해결하기 위해 equalTo() Matcher 메서드에 float로 명시적으로 지정해야 한다.

get("/odd").then().assertThat().body("odd.ck", equalTo(12.2f));

Request Method 구체화

일반적으로 우리는 사용하고자 하는 요청 메서드에 해당하는 get()과 같은 메서드를 호출하여 요청을 수행한다.

또한, request() 메서드를 사용하여 HTTP Method를 지정할 수도 있다.

@Test
public void whenRequestGet_thenOK(){
    when().request("GET", "/users/eugenp").then().statusCode(200);
}

위 예제는 get()을 직접 사용하는 것과 동일하다.

비슷하게, HEAD, CONNECT 및 OPTIONS 요청을 보낼 수도 있다.

@Test
public void whenRequestHead_thenOK() {
    when().request("HEAD", "/users/eugenp").then().statusCode(200);
}

POST 요청도 비슷한 구문을 따르며, with()body() 메서드를 사용하여 본문을 지정할 수 있다.

따라서 POST 요청을 보내어 새로운 Odd를 생성하려면 다음과 같이 작성하면 된다.

@Test
public void whenRequestedPost_thenCreated() {
    with().body(new Odd(5.25f, 1, 13.1f, "X"))
      .when()
      .request("POST", "/odds/new")
      .then()
      .statusCode(201);
}

Body로 요청된 Odd 객체는 자동으로 JSON으로 변환된다.

Default Configuration

테스트를 위해 여러 기본 값을 설정할 수 있다.

@Before
public void setup() {
    RestAssured.baseURI = "https://api.github.com";
    RestAssured.port = 443;
}

위 구성에서는 요청을 위한 기본 URI와 포트를 설정하고 있다. 이 외에도 기본 경로(base path), 루트 패스(root path), 그리고 인증(authentication)도 구성할 수 있다.

뿐만 아니라, 표준 REST-assured 기본값으로 재설정할 수 있다.

RestAssured.reset();

응답시간 측정

응답 시간을 측정하는 방법을 Response 객체의 time() 및 timeIn() 메서드를 사용하여 살펴보자.

@Test
public void whenMeasureResponseTime_thenOK() {
    Response response = RestAssured.get("/users/eugenp");
    long timeInMS = response.time();
    long timeInS = response.timeIn(TimeUnit.SECONDS);
    
    assertEquals(timeInS, timeInMS/1000);
}
  • time()은 응답 시간을 밀리초 단위로 가져오는 데 사용된다.
  • timeIn()은 지정된 시간 단위로 응답 시간을 가져오는 데 사용된다.

응답 시간 검증

응답 시간을 밀리초 단위로 간단한 long Matcher를 사용하여 검증할 수도 있다.

@Test
public void whenValidateResponseTime_thenSuccess() {
    when().get("/users/eugenp").then().time(lessThan(5000L));
}

만약 다른 시간 단위로 응답 시간을 유효성 검사하고 싶다면, time() 매처에 두 번째 TimeUnit 매개변수를 사용하면 된다.

@Test
public void whenValidateResponseTimeInSeconds_thenSuccess(){
    when().get("/users/eugenp").then().time(lessThan(5L), TimeUnit.SECONDS);
}

Logging

Log Request Details

log().all()을 사용하여 전체 요청 세부 정보를 로깅하는 방법을 살펴보자.

@DisplayName("예약 조회에 성공하다.")
@Test
public void getReservation() {
        RestAssured.get("/reservations/1")
                .then()
                .statusCode(200)
                .body("name", equalTo("초코칩"));
}

이렇게 하면 다음과 같이 로깅된다.

Request method:	GET
Request URI:	http://localhost:8080/reservations/1
Proxy:			<none>
Request params:	<none>
Query params:	<none>
Form params:	<none>
Path params:	<none>
Headers:		Accept=*/*
				Content-Type=application/json
Cookies:		<none>
Multiparts:		<none>

요청의 특정 부분만 로깅하려면 log() 메서드를 params(), body(), headers(), cookies(), method(), path()와 함께 사용할 수 있다. 예를 들어 log().params()와 같이 사용한다.

다른 라이브러리나 필터를 사용하는 경우 실제로 서버로 전송되는 내용이 변경될 수 있으므로 이 기능은 초기 요청 사양을 로깅하는 데만 사용해야 한다.

Log Response Details

유사하게, 응답 세부 정보를 로깅할 수도 있다.

@DisplayName("예약 조회에 성공하다.")
@Test
public void getReservation() {
        RestAssured.get("/reservations/1")
                .then()
                .statusCode(200)
                .body("name", equalTo("초코칩"));
}

출력은 다음과 같다.

{
  "id": 1,
  "name": "초코칩",
  "date": "2024-04-19",
  "time": "15:30:00"
}

Log Response if Condition Occurred

오류가 발생하거나 상태 코드가 지정된 값과 일치하는 경우에만 응답을 로깅할 수도 있다.

@Test
public void whenLogResponseIfErrorOccurred_thenSuccess() {
 
    when().get("/users/eugenp")
      .then().log().ifError();
    when().get("/users/eugenp")
      .then().log().ifStatusCodeIsEqualTo(500);
    when().get("/users/eugenp")
      .then().log().ifStatusCodeMatches(greaterThan(200));
}

Log if Validation Failed

검증이 실패한 경우에만 요청 및 응답을 로깅할 수도 있다.

@Test
public void whenLogOnlyIfValidationFailed_thenSuccess() {
    when().get("/users/eugenp")
      .then().log().ifValidationFails().statusCode(200);

    given().log().ifValidationFails()
      .when().get("/users/eugenp")
      .then().statusCode(200);
}

이 예제에서는 상태 코드가 200인지 확인하려고 한다. 이것이 실패하면 요청 및 응답이 로깅된다.

RestAssured vs MockMVC

사용 목적

  • RestAssured: RestAssured는 REST 웹 서비스를 검증하기 위한 라이브러리이며 대부분 End-to-End Test(전 구간 테스트)에 사용된다.

  • MockMVC: MockMvc는 웹 애플리케이션을 애플리케이션 서버에 배포하지 않고도 스프링 MVC의 동작을 재현할 수 있는 라이브러리이며 대부분 Controller Layer Unit Test(단위 테스트)에 사용된다.

속도

  • RestAssured: RestAssured는 별도의 구성없이 @WebMvcTest를 사용하지 못한다. 사용하기 위해선 아래와 같이 @SpringBootTest로 수행해야 한다. @SpringBootTest로 테스트를 수행하면 등록된 Spring Bean을 전부 로드하기 때문에 시간이 오래 걸린다.

  • MockMVC: MockMvc는 별도의 구성없이도 @WebMvcTest로 테스트를 수행할 수 있다. 물론 @SpringBootTest로도 수행할 수 있다.

속도가 빠른 @WebMvcTest와 별도의 구성이 필요 없는 MockMvc를 사용하는 게 무조건 좋은 것은 아니다. 상황에 따라 별도의 구성을 추가하는 비용을 지불하고 RestAssured를 사용할 수도 있고 Presentation Layer 외의 Bean을 여러 개 사용해야 한다면, 전부 Mock 객체로 처리해주는 비용과 빠른 테스트 시간 중 어느 것을 택할지도 고민해봐야 한다.

Ref

profile
초코칩처럼 달콤한 코드를 짜자

0개의 댓글