Maven + MockMvc 환경에서 Spring Rest Docs 설정하기

반응형
728x90
반응형

SpringBoot + Maven 프로젝트 생성

 

 

초기 셋팅 - 의존성

▶ pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.restdocs</groupId>
    <artifactId>spring-restdocs-mockmvc</artifactId>
    <scope>test</scope>
</dependency>

 

 

문서 작성을 위한 API 개발

 UserController

package com.maven.restapidocs.controller;

import com.maven.restapidocs.dto.ResponseDto;
import com.maven.restapidocs.dto.UserDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
@Slf4j
public class UserController {
    @GetMapping("/{userId}")
    public ResponseDto<UserDto> getUser(@PathVariable String userId){
        UserDto userDto = new UserDto();
        userDto.setUserId(userId);
        userDto.setUserName("테스트");
        userDto.setAge(27);

        return ResponseDto.<UserDto>builder()
                .status(HttpStatus.OK.value())
                .message("SUCCESS")
                .data(userDto)
                .build();
    }
}

 

 ResponseDto

package com.maven.restapidocs.dto;

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Builder
public class ResponseDto<T> {
    private int status;
    private String message;
    private T data;
}

 

 UserDto

package com.maven.restapidocs.dto;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class UserDto {
    private String userId;
    private String userName;
    private int age;
}

 

 

테스트 코드 작성

▶ UserControllerTest.java

package com.maven.restapidocs.controller;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.test.web.servlet.MockMvc;

import static com.maven.restapidocs.DocumentUtils.*;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@AutoConfigureRestDocs
@WebMvcTest(UserController.class)
class UserControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @Test
    void getUser() throws Exception {
        this.mockMvc.perform(RestDocumentationRequestBuilders.get("/user/{userId}", "seohae0001")
                )
                .andExpect(status().isOk())
                .andDo(document("get-user", getDocumentResponse(),
                        pathParameters(
                                parameterWithName("userId").description("사용자 아이디").attributes(getValidationAttribute("길이 : 8 ~ 12자" + newLine() + "조합 : 영문 + 숫자"))
                        ),
                        responseFields(
                                fieldWithPath("status").type(JsonFieldType.NUMBER).description("결과코드"),
                                fieldWithPath("message").type(JsonFieldType.STRING).description("결과메시지"),
                                fieldWithPath("data.userId").type(JsonFieldType.STRING).description("유저아이디").attributes(getExampleAttribute("예) seohae1234")),
                                fieldWithPath("data.userName").type(JsonFieldType.STRING).description("유저명").attributes(getExampleAttribute("예) 김서해")),
                                fieldWithPath("data.age").type(JsonFieldType.NUMBER).description("나이")
                        )
                ));
    }
}

1) pathParameters 설정

pathParameters(
        parameterWithName("userId").description("사용자 아이디").attributes(getValidationAttribute("길이 : 8 ~ 12자" + newLine() + "조합 : 영문 + 숫자"))
),

 

2) responseFields 설정

responseFields(
        fieldWithPath("status").type(JsonFieldType.NUMBER).description("결과코드"),
        fieldWithPath("message").type(JsonFieldType.STRING).description("결과메시지"),
        fieldWithPath("data.userId").type(JsonFieldType.STRING).description("유저아이디").attributes(getExampleAttribute("예) seohae1234")),
        fieldWithPath("data.userName").type(JsonFieldType.STRING).description("유저명").attributes(getExampleAttribute("예) 김서해")),
        fieldWithPath("data.age").type(JsonFieldType.NUMBER).description("나이")
)

 

▶ UserControllerTest.java

package com.maven.restapidocs;

import org.springframework.restdocs.operation.preprocess.OperationPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor;
import org.springframework.restdocs.operation.preprocess.Preprocessors;
import org.springframework.restdocs.snippet.Attributes;

import static org.springframework.restdocs.snippet.Attributes.key;

public interface DocumentUtils {
    /**
     * request pretty
     * @return
     */
    static OperationRequestPreprocessor getDocumentRequest() {
        return Preprocessors.preprocessRequest(new OperationPreprocessor[]{Preprocessors.prettyPrint()});
    }

    /**
     * response pretty
     * @return
     */
    static OperationResponsePreprocessor getDocumentResponse() {
        return Preprocessors.preprocessResponse(new OperationPreprocessor[]{Preprocessors.prettyPrint()});
    }

    /**
     * Text 줄바꿈
     * @return
     */
    static String newLine() {
        return " +" + "\n";
    }

    /**
     * example 속성
     * @param example
     * @return
     */
    static Attributes.Attribute getExampleAttribute(Object example) {
        return key("example").value(example);
    }

    /**
     * validation 속성
     * @param validation
     * @return
     */
    static Attributes.Attribute getValidationAttribute(Object validation) {
        return key("validation").value(validation);
    }
}

1) Json pretty 설정

/**
 * request pretty
 * @return
 */
static OperationRequestPreprocessor getDocumentRequest() {
    return Preprocessors.preprocessRequest(new OperationPreprocessor[]{Preprocessors.prettyPrint()});
}

/**
 * response pretty
 * @return
 */
static OperationResponsePreprocessor getDocumentResponse() {
    return Preprocessors.preprocessResponse(new OperationPreprocessor[]{Preprocessors.prettyPrint()});
}

 

2) Docs 문서 안에 줄바꿈 처리는 아래와 같이 처리

static String newLine() {
    return " +" + "\n";
}

 

 

문서를 생성해보자!

[1] 테스트 코드 실행

[2] 테스트 성공

 

[3] target/generated-snippets 경로에 .adoc 파일 생성 여부 확인

 

[.adoc]
  • curl-request.adoc
[source,bash]
----
$ curl 'http://localhost:8080/user/seohae0001' -i -X GET
----

 

  • http-request.adoc
[source,http,options="nowrap"]
----
GET /user/seohae0001 HTTP/1.1
Host: localhost:8080

----

 

  • http-response.adoc
[source,http,options="nowrap"]
----
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 137

{
  "status" : 200,
  "message" : "SUCCESS",
  "data" : {
    "userId" : "seohae0001",
    "userName" : "테스트",
    "age" : 27
  }
}
----

 

  • httpie-request.adoc
[source,bash]
----
$ http GET 'http://localhost:8080/user/seohae0001'
----

 

  • path-parameters.adoc
|===
|Path|Required|Description|Validation

|`+userId+`
|true
|사용자 아이디
|길이 : 8 ~ 12자 +
조합 : 영문 + 숫자

|===

 

  • request-body.adoc
[source,options="nowrap"]
----

----

 

  • response-body.adoc
[source,options="nowrap"]
----
{
  "status" : 200,
  "message" : "SUCCESS",
  "data" : {
    "userId" : "seohae0001",
    "userName" : "테스트",
    "age" : 27
  }
}
----

 

  • response-fields.adoc
|===
|Path|Type|Required|Description|Example

|`+status+`
|`+Number+`
|true
|결과코드
|

|`+message+`
|`+String+`
|true
|결과메시지
|

|`+data.userId+`
|`+String+`
|true
|유저아이디
|예) seohae1234

|`+data.userName+`
|`+String+`
|true
|유저명
|예) 김서해

|`+data.age+`
|`+Number+`
|true
|나이
|

|===

 

[4] src/main/asciidoc 경로에 .adoc 파일 수기 생성 (API Docs 문서의 형식 셋팅)

 

[5] get-user.adoc 파일 수기 생성

= API : 사용자 정보 조회 
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 2
:sectlinks:

[[overview]]
== Overview
operation::get-user[snippets='curl-request']

[[overview-request-dto]]
=== Request
operation::get-user[snippets='path-parameters']

=== Request Example
operation::get-user[snippets='http-request']

[[overview-response-dto]]
=== Response
operation::get-user[snippets='response-fields']

=== Response Example
operation::get-user[snippets='response-body']

■ snippets='.aodc 파일명'을 적으면 된다.

 

■ operation::get-user 에서 'get-user'은 아래 테스트 코드 작성시의 identifier와 동일하게 작성한다.

 

[6] 크롬 아이콘을 클릭해보자.

 

[7] 문서 구조 확인

 

 

html 파일 생성

우리가 위에서 생성한 .adoc 파일 기준으로 .html 파일을 생성해보자.

 

아래 2가지 경로에 .html 파일이 생성된다.

1) target/generated-docs

2) target/classes/static/docs

 

 

Docs 안의 snippet 커스텀 생성하기

기존에 제공되는 default-**.snppet은 아래의 경로에서 확인 가능하다.

org.springframework.restdocs.templates.asciidoctor

 

커스텀할 snippet 파일을 아래의 경로에 추가하여 default snippet 대신, 내가 원하는 형식의 snippet으로 생성되도록 설정해보자.

 

경로 생성 : src/test/resources/org/springframework/restdocs/templates/asciidoctor

 

▶ path-parameters.snippet

|===
|Path|Required|Description|Validation

{{#parameters}}
|{{#tableCellContent}}`+{{name}}+`{{/tableCellContent}}
|{{#tableCellContent}}{{^optional}}true{{/optional}}{{#optional}}false{{/optional}}{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
|{{#tableCellContent}}{{#validation}}{{.}}{{/validation}}{{/tableCellContent}}

{{/parameters}}
|===

 

설정 로직

1) UserControllerTest.java

pathParameters(
        parameterWithName("userId").description("사용자 아이디").attributes(getValidationAttribute("길이 : 8 ~ 12자" + newLine() + "조합 : 영문 + 숫자"))
),

 

2) DocumentUtils.java

/**
 * validation 속성
 * @param validation
 * @return
 */
static Attributes.Attribute getValidationAttribute(Object validation) {
    return key("validation").value(validation);
}

key에 셋팅된 "validation"이 바로 위 snippet 문서의 문법 중 validation 영역에 해당된다.

 

3) 생성된 문서 영역

 

▶ response-fields.snippet

|===
|Path|Type|Required|Description|Example

{{#fields}}
|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}}
|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}}
|{{#tableCellContent}}{{^optional}}true{{/optional}}{{#optional}}false{{/optional}}{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
|{{#tableCellContent}}{{#example}}{{.}}{{/example}}{{/tableCellContent}}

{{/fields}}
|===

 

설정 로직

1) UserControllerTest.java

fieldWithPath("data.userId").type(JsonFieldType.STRING).description("유저아이디").attributes(getExampleAttribute("예) seohae1234")),
fieldWithPath("data.userName").type(JsonFieldType.STRING).description("유저명").attributes(getExampleAttribute("예) 김서해")),

 

2) DocumentUtils.java

/**
 * example 속성
 * @param example
 * @return
 */
static Attributes.Attribute getExampleAttribute(Object example) {
    return key("example").value(example);
}

 

3) 생성된 문서 영역

 

 

문서의 Json Pretty 설정하기

Before

Test 코드

 문서 내용

 

After

 Test 코드

 문서 내용

 

 

Response List 객체 

UserList 조회 API 추가해보자.

 

▶ UserController.java

...
    @GetMapping("/users")
    public ResponseDto<List<UserDto>> getUsers(){
        UserDto userDto = new UserDto();
        userDto.setUserId("test1");
        userDto.setUserName("테스트1");
        userDto.setAge(27);

        UserDto userDto2 = new UserDto();
        userDto2.setUserId("test2");
        userDto2.setUserName("테스트2");
        userDto2.setAge(25);

        List<UserDto> list = new ArrayList<>();
        list.add(userDto);
        list.add(userDto2);

        return ResponseDto.<List<UserDto>>builder()
                .status(HttpStatus.OK.value())
                .message("SUCCESS")
                .data(list)
                .build();
    }
...

 

▶ UserControllerTest.java

...
    @Test
    void getUserList() throws Exception {
        this.mockMvc.perform(RestDocumentationRequestBuilders.get("/user/users")
                )
                .andExpect(status().isOk())
                .andDo(document("get-users", getDocumentResponse(), responseFields(
                        fieldWithPath("status").type(JsonFieldType.NUMBER).description("결과코드"),
                        fieldWithPath("message").type(JsonFieldType.STRING).description("결과메시지"),
                        fieldWithPath("data[].userId").type(JsonFieldType.STRING).description("유저아이디").attributes(getExampleAttribute("예) seohae1234")),
                        fieldWithPath("data[].userName").type(JsonFieldType.STRING).description("유저명").attributes(getExampleAttribute("예) 김서해")),
                        fieldWithPath("data[].age").type(JsonFieldType.NUMBER).description("나이")
                )));
    }
...

1) 리스트의 경우 []를 추가한다.

fieldWithPath("data[].userId").type(JsonFieldType.STRING).description("유저아이디").attributes(getExampleAttribute("예) seohae1234")),
fieldWithPath("data[].userName").type(JsonFieldType.STRING).description("유저명").attributes(getExampleAttribute("예) 김서해")),
fieldWithPath("data[].age").type(JsonFieldType.NUMBER).description("나이")

 

 get-users.adoc

= API : 사용자 정보 조회 
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 2
:sectlinks:curl-request.adoc

[[overview]]
== Overview
operation::get-users[snippets='curl-request']

[[overview-request-dto]]
=== Request
operation::get-users[snippets='path-parameters']

=== Request Example
operation::get-users[snippets='http-request']

[[overview-response-dto]]
=== Response
operation::get-users[snippets='response-fields']

=== Response Example
operation::get-users[snippets='response-body']

 

▶ 문서 생성 모습

 

 

반응형

Designed by JB FACTORY