RestAssured 살펴보기

후추·2023년 5월 14일
3

들어가기

자바, 스프링으로 웹 애플리케이션을 개발할 때 RestAssured 로 테스트 코드를 작성했다.

RestAssured 가 무엇인지, 어떻게 활용해볼 수 있는지 정리한다.

RestAssured

소개

REST Assured is a Java DSL for simplifying testing of REST based services built on top of HTTP Builder. It supports POST, GET, PUT, DELETE, OPTIONS, PATCH and HEAD requests and can be used to validate and verify the response of these requests. - docs

RestAssured는 RESTful 웹 서비스를 테스트하기 위한 라이브러리이다.

RestAssured를 통해 POST, GET, PUT, DELETE 등과 같은 요청을 보낼 수 있고, 그 응답을 확인하고 검증할 수 있다.

예시

어떤 웹 서비스가 특정 URL(http://localhost:8080/lotto)로 GET 요청을 보내면, 다음과 같은 JSON을 응답한다고 하자.

{
  "lotto": {
    "lottoId": 5,
    "winning-numbers": [2, 45, 34, 23, 7, 5, 3],
    "winners": [
      {
        "winnerId": 23,
        "numbers": [2, 45, 34, 23, 3, 5]
      },
      {
        "winnerId": 54,
        "numbers": [52, 3, 12, 11, 18, 22]
      }
    ]
  }
}

RestAssured를 이용해 직접 GET 요청을 보내고 응답을 검증할 수 있다.

예를 들어 응답의 lottoId가 5이고, winnerId가 23, 54인 것을 확인해보고 싶다면 다음과 같이 코드를 작성할 수 있다.

	@Test
    void lotto_test(){
        RestAssured.get("/lotto")
                .then()
                .body("lotto.lottoId", equalTo(5))
                .body("lotto.winners.winnerId", hasItems(23, 54));
    }

특징

RestAssured의 특징은 다음과 같다.

1. 간결한 구문

RestAssured 는 직관적이고 간결한 구문을 제공한다. 사용자는 자연어와 유사한 구문을 사용하여 요청을 작성할 수 있고, 체이닝 방식을 통해 다양한 검증을 수행할 수 있다. 예를 들어 RestAssured 는 Given - When - Then 패턴을 지원한다. 이를 통해 사용자는 테스트 코드의 가독성을 높은 수준으로 유지할 수 있다.

2. HTTP 메서드 지원

RestAssured 는 위에서 언급했듯 GET,POST, PUT, DELETE 등 다양한 HTTP 메서드를 지원한다. 사용자는 API에 요청을 보낼 수 있고 그 기능을 테스트할 수 있다.

3. 쉬운 응답 검증

RestAssured 는 JSON 및 XML과 같은 다양한 형식의 응답을 쉽게 파싱해준다. 따라서 헤더, 상태 코드를 비롯해 응답 본문도 쉽게 검증할 수 있다.

RestAssured 활용법

본격적으로 RestAssured 로 활용법을 알아보자.

문법

RestAssured 는 Given- When - Then 패턴을 기본틀로 갖는다. 각각의 절이 어떤 역할을 하는지 살펴보자.

given(). 
        param("x", "y"). 
        header("z", "w").
when().
		//Method().
then(). 
        statusCode(XXX).
        body("x, ”y", equalTo("z"));

1. given

  • given 절은 테스트의 "준비 단계"를 정의하는 부분이다.
  • given 을 통해 요청의 header, query string, path variable, body, cookies 등을 설정할 수 있다.
  • 이를 통해 인증된 사용자로 로그인한 상태를 가정하거나, 특정 요청 헤더를 설정하는 등의 사전 설정 작업을 할 수 있다.
  • 만약 요청에 어떠한 사전 설정도 필요하지 않은 경우, given 은 생략할 수 있다.

2. when

  • when 절은 테스트의 "동작 단계"를 정의하는 부분이다.
  • when 절은 API 요청을 보내는 작업을 수행한다.
  • Method() 부분에 get, post, put, delete 등의 HTTP 메서드가 위치하게 되며, path를 지정해야 한다.

3. then

  • then 절은 테스트의 "검증 단계"를 정의하는 부분이다.
  • then 절에서 API 응답을 검증한다.
  • 메서드 체이닝을 통해, 상태 코드, 응답의 내용, 헤더 정보 등을 검증할 수 있다.
  • RestAssured가 아닌 AssertJ로 검증하고 싶은 경우 응답을 추출할 수 있다.

요청 설정하기

Query String(Query Parameter)

URL에 Query parameter를 지정할 수 있다.

param()

parameter 를 지정하기 위해 param()을 사용할 수 있다.

// URL : /members?name=huchu&age=26 
given()
	.param("name", "huchu")
    .param("age", "26")
.when()
    .get("/members")
       ...

가변인자를 통해 2개 이상의 값을 넣을 수 있다.

// URL : /members?name=huchu&name=Combi153
given()
	.param("name", "huchu", "Combi153")
.when()
    .get("/members")
       ...

혹은 List를 사용할 수 있다.

// URL : /members?name=huchu&name=Combi153
final List<String> values = List.of("huchu", "Combi153");

given()
	.param("name", values)
.when()
    .get("/members")
       ...

queryParam()

query parameter와 form parameter를 명시적으로 구분하기 위해 queryParam()을 사용할 수 있다.

// URL : /members?name=huchu&age=26 
given()
	.queryParam("name", "huchu")
    .queryParam("age", "26")
.when()
    .get("/members")
       ...

직접 설정

URL에 직접 설정할 수 있다.

// URL : /members?name=huchu&age=26 
.when()
    .get("/members?name=huchu&age=26")
       ...

Path variable(Path parameters)

URL에 Path parameter를 지정할 수 있다.

pathParam()

parameter 를 지정하기 위해 pathParam()을 사용할 수 있다.

// URL : /members/1/carts/1
given()
	.pathParam("memberId", 1)
    .pathParam("cartId", 1)
.when()
	.get("/members/{memberId}/carts/{cartId}")
    	...

pathParams()

여러 개의 parameter 를 지정하고 싶다면 pathParams()를 사용할 수 있다.

pathParams를 사용하면 가변인자로 여러 개의 parameter 를 지정할 수 있다.

// URL : /members/1/carts/1
given()
	.pathParams("memberId", 1, "cartId", 1) 
.when()
	.get("/members/{memberId}/carts/{cartId}")
    	...

또한 Map을 통해 지정할 수 있다.

// URL : /members/1/carts/1
final Map<String, Integer> pathMap = Map.of(
                "memberId", 1,
                "cartId", 1
        );

given()
	.pathParams(pathMap) 
.when()
	.get("/members/{memberId}/carts/{cartId}")
    	...

직접 설정

URL에 직접 설정할 수 있다.

// URL : /members/1/carts/1
when()
	.get("/members/{memberId}/carts/{cartId}", "1", "1")
    	...

header()

header() 메서드를 통해 설정할 수 있다.

given().log().all()
	.header("Header1", "something")
.when()
	...

headers()

header를 한번에 여러 가지 설정하고 싶다면 headers()를 이용한다.

given().log().all()
	.headers("Header1", "something", "Header2", "something")
.when()
	...
final Map<String, String> headerMap = Map.of(
                "Header1", "something",
                "Header2", "something"
        );

given().log().all()
	.headers(headerMap)
.when()
	...

Content Type

Content-Type 은 HTTP 메시지에 담긴 데이터의 형식을 명시하는 header이다.

이 header는 클라이언트와 서버 간에 전송하는 데이터의 형식을 명시할 때 사용할 수 있다.

header의 내용은 contentType() 메서드에 직접 작성하거나, RestAssured의 ContentType 상수 혹은 스프링의 MediaType 상수를 사용할 수 있다.

given().log().all()
	.contentType(MediaType.APPLICATION_JSON_VALUE) // "application/json" 혹은 ContentType.JSON
    .body(...)
    	...

Accept

Accept 는 클라이언트가 원하는 응답 데이터의 형식을 지정하는 header이다.

클라이언트는 Accept header를 통해 서버에게 선호하는 응답 형식을 알려줄 수 있다.

만약 클라이언트가 "Accept: application/json" header를 포함한 요청을 보낸다면, 서버는 JSON 형식의 응답을 반환할 수 있다. (반드시 반환하는 것은 아니다.)

header의 내용은 accept() 메서드에 직접 작성하거나, RestAssured의 ContentType 상수 혹은 스프링의 MediaType 상수를 사용할 수 있다.

given().log().all()
	.accept(MediaType.APPLICATION_JSON_VALUE)
.when()
    ...

body

요청 body는 body() 메서드로 설정할 수 있다.

body() 는 매개변수로 문자열뿐만 아니라 다양한 객체를 받을 수 있다.

body() 에 담는 값을 JSON 등의 특정한 형식으로 보내고 싶다면, ContentType을 설정하면 된다.

ContentType을 명시하면 RestAssured는 body에 담긴 값을 ContentType에 맞게 직렬화해서 요청을 보낸다.

User user = new User("huchu", "huchu@email.com");

.given().log().all()
	.contentType(MediaType.APPLICATION_JSON_VALUE)
	.body(user)
.when()
	.post("/users")
    	...
final String jsonContent = "{\n" +
                "    \"id\": null,\n" +
                "    \"name\": \"huchu\",\n" +
                "    \"email\": \"huchu@email.com\"\n" +
                "}";

.given().log().all()
	.contentType(MediaType.APPLICATION_JSON_VALUE)
	.body(jsonContent)
.when()
	.post("/users")
    	...

응답 검증하기

then 절에서 서버의 응답에 대한 검증을 진행한다.

Cookie, Status, Header, Content-Type, Body 등을 검증할 수 있다.

검증 시 assertThat은 생략할 수 있다.

Cookies

get("/x").then().assertThat().cookie("cookieName", "cookieValue"). ..
get("/x").then().assertThat().cookies("cookieName1", "cookieValue1", "cookieName2", "cookieValue2"). ..
get("/x").then().assertThat().cookies("cookieName1", "cookieValue1", "cookieName2", containsString("Value2")). ..

Status

get("/x").then().assertThat().statusCode(200). ..
get("/x").then().assertThat().statusLine("something"). ..
get("/x").then().assertThat().statusLine(containsString("some")). ..

Headers

get("/x").then().assertThat().header("headerName", "headerValue"). ..
get("/x").then().assertThat().headers("headerName1", "headerValue1", "headerName2", "headerValue2"). ..
get("/x").then().assertThat().headers("headerName1", "headerValue1", "headerName2", containsString("Value2")). ..

Content-Type

get("/x").then().assertThat().contentType(ContentType.JSON). ..

body

body를 검증할 때는 path를 지정해서 원하는 부분을 검증할 수 있다.

만약 path를 지정하지 않는 경우 body 전체를 가리키게 된다.

전체 검증

HTTP/1.1 200 
Content-Type: application/json
Content-Length: 7
Date: Sun, 14 May 2023 14:10:13 GMT
Keep-Alive: timeout=60
Connection: keep-alive

message

body()에 path를 지정해주지 않으면 전체 본문을 확인한다.

위와 같은 응답에 대해 아래와 같은 방법으로 body의 전체 본문을 확인할 수 있다.

get("/x")
	.then().log().all()
    .statusCode(HttpStatus.OK.value())
    .body(equalTo("message"));

부분 검증

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sun, 14 May 2023 14:15:09 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
    "id": 1,
    "name": "huchu",
    "email": "huchu@email.com"
}

body()에 path를 지정해주면 해당 값을 확인한다.

위와 같은 응답에 대해 아래와 같이 body의 세부 부분을 확인할 수 있다.

get("/x")
	.then().log().all()
    .statusCode(HttpStatus.OK.value())
    .body("id", equalTo(1))
    .body("name", equalTo("huchu"))
    .body("email", equalTo("huchu@email.com"));

org.hamcrest.Matchers

RestAssured의 검증 부분에서는 org.hamcrest.Matchers의 메서드를 이용할 수 있다.

다음은 자주 사용되는 메서드들이다.

값 검증

  • equalTo(expectedValue): 예상값과 실제값이 동일한지 비교한다.
  • not(equalTo(unexpectedValue)): 예상하지 않은 값과 실제값이 동일하지 않은지 비교한다.

컬렉션 검증

  • hasItem(expectedItem): 컬렉션에 특정 항목이 포함되어 있는지 확인한다.
  • hasItems(item1, item2, ...): 컬렉션에 여러 항목이 모두 포함되어 있는지 확인한다.
  • hasSize(expectedSize): 검증 대상 컬렉션의 크기가 예상 크기와 일치하는지 확인한다.

값 검증

  • greaterThan(expectedValue): 예상값보다 큰지 확인한다.
  • lessThan(expectedValue): 예상값보다 작은지 확인한다.
  • greaterThanOrEqualTo(expectedValue): 검증 대상 값이 주어진 값보다 크거나 같은지 확인한다.
  • lessThanOrEqualTo(expectedValue): 검증 대상 값이 주어진 값보다 작거나 같은지 확인한다.

null 혹은 빈 값 검증

  • nullValue(): 값이 null인지 확인한다.
  • notNullValue(): 값이 null이 아닌지 확인한다.
  • isEmpty(): 검증 대상 객체가 비어있는지 확인합니다. (컬렉션이나 문자열)
  • isNotEmpty(): 검증 대상 객체가 비어있지 않은지 확인합니다. (컬렉션이나 문자열)

문자열 검증

  • equalToIgnoringCase(expectedValue): 대소문자를 무시하고 예상값과 동일한지 확인한다.
  • containsString(substring): 문자열에 특정 부분 문자열이 포함되어 있는지 확인한다.
  • startsWith(expectedPrefix): 검증 대상 문자열이 주어진 접두사로 시작하는지 확인한다.
  • endsWith(expectedSuffix): 검증 대상 문자열이 주어진 접미사로 끝나는지 확인한다.

Logging

RestAssured는 요청과 응답의 값을 확인할 수 있게 log 기능을 제공한다.

요청 Logging

all()을 설정하면 모든 세부 사항을 출력해준다.

그밖에도 원하는 부분만 출력할 수 있는 메서드가 다양하게 제공된다.

given().log().all(). .. // Log all request specification details including parameters, headers and body
given().log().params(). .. // Log only the parameters of the request
given().log().body(). .. // Log only the request body
given().log().headers(). .. // Log only the request headers
given().log().cookies(). .. // Log only the request cookies
given().log().method(). .. // Log only the request method
given().log().path(). .. // Log only the request path

응답 Logging

요청 Logging과 비슷하다.

모든 세부사항을 출력하거나, 원하는 부분을 선택할 수 있다.

언제 로그를 출력할지 조건을 선택할 수도 있다.

get("/x").then().log().all(). .. 
get("/x").then().log().body() ..
get("/x").then().log().statusLine(). .. // Only log the status line
get("/x").then().log().headers(). .. // Only log the response headers
get("/x").then().log().cookies(). .. // Only log the response cookies

get("/x").then().log().ifError(). .. 

get("/x").then().log().ifStatusCodeIsEqualTo(302). .. // Only log if the status code is equal to 302
get("/x").then().log().ifStatusCodeMatches(matcher). .. // Only log if the status code matches the supplied Hamcrest matcher

Reference

https://rest-assured.io/
https://github.com/rest-assured/rest-assured/wiki/Usage
https://www.guru99.com/rest-assured.html
https://loopstudy.tistory.com/427
chatGPT

1개의 댓글

comment-user-thumbnail
2023년 5월 21일

압도적 감사...

답글 달기

관련 채용 정보