Test

RestAssured로 REST API 테스트하기

KAispread 2023. 9. 28. 19:30
728x90
반응형

🔹 개요

E2I 백엔드 팀은 MVC의 각 계층별로 다음과 같이 테스트를 작성하고 있었다. 

- Controller: 단위 테스트 (Mocking)

- Service: 단위, 통합 테스트 (Mocking, SpringBootTest)

- Repository: 단위, 통합 테스트 (DataJpaTest, SpringBootTest)

Controller 계층의 테스트를 단위 테스트로 작성한 이유는 Service 계층에서 이미 통합 테스트를 작성하고 있었기 때문에, Controller Layer에서는 들어오는 값에 대한 Validator만 제대로 동작하면 실제 API가 동작하는데에도 문제가 없을 것이라 생각했기 때문이다.

하지만, 프론트와 API를 연동하는 과정에서 개발 당시에 생각하지 못했던 다양한 문제가 발생했다. 따라서, 클라이언트 레벨에서도 API가 제대로 동작하는지 테스트해야겠다는 생각이 들었고 E2E Test 와, 일부 상황에 대해서 인수 테스트까지 도입하기로 결정했다.

이에 대한 테스트 도구로, Spring의 WebMvcTest 또는 webClient 를 사용하는 방법, REST Assured 를 사용하는 방법 등 여러 방법이 있다는 것을 알게되었고 BDD 스타일로 테스트를 작성할 수 있는 REST Assured를 사용하기로 결정했다.

 

🔸 REST Assured 란?

공식 문서에서는 REST Assured를 다음과 같이 소개하고 있다.

https://rest-assured.io/

 

REST Assured

Intro News Release Notes Docs Who Testing and validating REST services in Java is harder than in dynamic languages such as Ruby and Groovy. REST Assured brings the simplicity of using these languages into the Java domain. For example, if your HTTP server r

rest-assured.io

"자바에서 REST 서비스를 테스트하고 검증하는 것은 Ruby나 Groovy와 같은 동적 언어보다 어렵습니다. REST Assured는 이러한 언어를 Java 도메인에 간편하게 사용할 수 있도록 지원합니다"

공식 문서에서 소개된 바와 같이, REST Assured는 Rest 서비를 테스트하기위한 도구이다. BDD style (give - when - then)로 테스트를 작성하며 Spring Boot Application을 띄워 테스트를 진행하므로 클라이언트 레벨에서 테스트를 진행할 수 있다.

 


 

📌 Getting Started

REST Assured 의존성 추가

// REST - Assured
testImplementation 'io.rest-assured:rest-assured:5.3.2'

REST Assured 를 사용하기 위해 build.gradle 에 다음의 코드를 추가한다.

참고

Notes
1. You should place rest-assured before the JUnit dependency declaration in your pom.xml / build.gradle in order to make sure that the correct version of Hamcrest is used.
2. REST Assured includes JsonPath and XmlPath as transitive dependencies

공식문서가 안내하는대로 제대로된 버전의 Hamcrest을 사용하기 위해 JUnit 의존성 앞에 REST Assured를 추가하자.

REST Assured는 Json 형태의 응답을 JsonPath객체로 직렬화하여 저장한다. 이를 위해, jsonPath 의존성이 필요한데 이는 io.rest-assured:rest-assured 에 포함되어있다.

 

 

Request :: given - when - then

given - when - then 형태로 API 요청을 전송한다. 각 절은 요청 값 세팅 / 요청 경로 / 응답 값을 세팅한다.

@Test
void registration() {
    RegisterRequest request = new RegisterRequest();

    RestAssured
    	.given()
            .contentType(ContentType.JSON)
            .header("Authorization", token)
            .body(request)
            .log().all()
        .when()
            .post("/v1/member")
        .then()
            .log().all()
            .extract();
}

given

  • given 절은 요청에 필요한 데이터를 세팅하는 구간이다.
  • contentType, cookie, header, queryParam, body 등 REST API 요청에 필요한 모든 값을 세팅할 수 있다. 

when

  • 요청을 전송할 URL 과 HTTP method를 지정하는 구간이다.

then

  • REST API 요청에 대한 응답 값을 받아볼 수 있는 구간이다.
  • then절 이후 제공되는 API들을 통해 값을 검증할 수 있다.
  • 하지만 보통 extract()로 얻은 ExtractableResponse 객체로 Assertions API를 통해 검증을 진행한다.
log().all()
given() / then() 절에서 log().all() 를 설정하면 다음과 같이 요청, 응답 값에 대한 상세 정보를 콘솔에 남길 수 있다.

 

 

Response :: ExtractableResponse<R>

RestAssured로 전송한 요청에 대한 응답으로 ExtractableResponse 객체를 얻을 수 있다. 이 객체는 REST Assured 요청에 대한 응답값을 저장하고 있어, body(), cookie(), header() 와 같은 메서드를 통해 응답 값에 대한 정보를 얻을 수 있다.

참고로 ExtractableResponse 객체는 다음과 같은 상속관계를 가지고 있다.

 

@Test
void registration() {
    // given
    RegisterRequest request = new RegisterRequest();

    // when
    ExtractableResponse<Response> response = RestAssured
    	.given()
            .contentType(ContentType.JSON)
            .header("Authorization", token)
            .body(request)
        .when()
            .post("/v1/member")
        .then()
            .extract();
    
    // then
    // JsonPath 가져오기
    final JsonPath jsonPath = response.body().jsonPath();
    final String nickname = jsonPath.getString("data.nickname");
    final boolean authStatus = jsonPath.getBoolean("data.authUnivStatus");
}

Json 포맷으로 반환되는 응답에 대한 body값은 다음과 같이 JsonPath 인스턴스와 json 경로 표현식을 통해 가져올 수 있다.

get() 을 통해서도 값을 가져올 수 있지만, getString(), getBoolean 과 같은 메서드를 수행하면 가져오는값에 대한 타입 캐스팅이 가능하다. 보다 명확한 검증을 위해 후자의 방법을 통해 값을 가져오는 것을 추천한다. 

JsonPath 인스턴스가 제공하는 다양한 get

 

 

테스트 작성

REST Assured를 통해 회원가입 API를 검증하기 위해 다음과 같이 테스트를 작성하였다.

@TestPropertySource(locations = "classpath:application-rest.yml")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MemberRestTest {

    @LocalServerPort
    int port;

    @BeforeEach
    void setUp() {
        RestAssured.port = port;
    }

    @DisplayName("회원 가입을 한다.")
    @Test
    void 회원가입을_한다() {
        // given
        final CreateMemberRequestDto request = KAI.createMemberRequestDto();

        // when
        final String url = "/v1/member";
        ExtractableResponse<Response> response = RestAssured
        	.given()
            	.contentType(ContentType.JSON)
            	.body(request)
            	.log().all()
            .when()
            	.post(url)
            .then()
            	.log().all()
            	.extract();

        //then
        JsonPath jsonPath = response.body().jsonPath();
        assertAll(
            () -> assertThat(response.statusCode()).isEqualTo(200),
            () -> assertThat(response.header(ACCESS.getKey())).isNotNull(),
            () -> assertThat(jsonPath.getString("status")).isEqualTo("SUCCESS"),
            () -> assertThat(jsonPath.getString("message")).isEqualTo("Create Member Success")
        );
    }
}
  • REST Assured는 직접 Spring Boot Application을 띄워 진행하는 테스트이기 때문에 별도로 포트를 지정한다.
  • RestAssured API의 given - when - then 절을 통해 API 요청을 전송하고 응답값을 받아온다.
  • assertJ API를 통해 값을 검증한다.

회원가입 요청에 대한 JSON 포맷의 응답값

 

 

Matcher

앞선 테스트 코드에서는 ExtractableResponse 인스턴스를 받아, assertJ의 API를 사용하여 검증을 수행했다.

ExtractableResponse 인스턴스를 가져오지 않고, 다음과 같이 org.hamcrest.Matcheris(), equal(), contains() 등의 검증 API를 사용하여 테스트를 수행할 수 있다.

// when
final String url = "/v1/member";
ExtractableResponse<Response> response = RestAssured
    .given()
        .contentType(ContentType.JSON)
        .body(request)
        .log().all()
    .when()
        .post(url)
    .then()
        .log().all()
        // Assertion
        .body("status", org.hamcrest.Matchers.is("SUCCESS"))
        .body("message", org.hamcrest.Matchers.equalTo("Create Member Success"));

 

 

 

MultipartFormData

@DisplayName("개인 프로필 사진을 업로드 할 수 있다.")
@Test
void uploadProfile() {
    // given
    final ProfileRequest request = new ProfileRequest();
    final String accessToken = getAccessToken();
    Resource resource = resourceLoader.getResource("classpath:test.png");

    // when
    ExtractableResponse<Response> response = RestAssured
        .given()
            .contentType(ContentType.MULTIPART)
            .header("Authorization", accessToken)
            .multiPart("file", resource.getFile())
            .multiPart("data", request)
            .log().all()
        .when()
            .post(url)
        .then()
            .log().all()
            .extract();
    
    // then
    assertThat(response.statusCode()).isEqualTo(200);
}
  • 다음과 같이, MultiPartFormData 요청도 전송할 수 있다.

 

 

Spring Security

Spring Security를 사용하는 경우, 다음과 같이 @WithMockUser을 통해 인증객체 (principal)을 주입할 수 있다.

@WithMockUser(username = "authorized_user")
@Test public void
spring_security_mock_annotations_example() {
    given().
            webAppContextSetup(context).
     when().
            get("/user-aware").
     then().
            statusCode(200).
            body(equalTo("Success")).
            expect(authenticated().withUsername("authorized_user"));
}

 

더 자세한 내용은 다음 문서에서 확인 할 수 있다.

https://github.com/rest-assured/rest-assured/wiki/Spring#using-spring-security-test

 

Spring

Java DSL for easy testing of REST services. Contribute to rest-assured/rest-assured development by creating an account on GitHub.

github.com

 

 

📝 Reference

https://rest-assured.io/

- https://github.com/rest-assured/rest-assured/wiki/GettingStarted#maven--gradle-users

- https://github.com/rest-assured/rest-assured/wiki/Usage

- https://github.com/rest-assured/rest-assured/wiki/Spring#injecting-a-user

728x90
반응형