본문 바로가기

Backend/SpringBoot

Spring Boot에서 Spring Rest Docs 사용해보기

안녕하세요! 이번 포스팅에서는 Spring Rest Docs를 적용하고 사용하는 방법에 대해 알아보겠습니다.

전체 코드는 Github에서 확인이 가능합니다. ✍️

 

Spring Rest Docs는 API들을 자동으로 문서화할 수 있게 도와주는 도구입니다. 비슷한 서비스로는 Swagger가 있습니다. 저도 초반에는 Swagger를 사용했는데, 사용하면서 아래와 같은 불편함을 느꼈습니다.

 

1. 서비스 코드에 Swagger 관련 어노테이션을 추가해줘야한다.

@Api
@Controller
public class AccountController {

  @ApiOperation("회원정보 가져오기")
  public ResponseEntity<RestResponse> changeInfo() throws BizCheckedException {
    // ...
  }	

  @ApiIgnore
  public String list() {
    // ...
  }
}

2. 테스트는 용이하나 Swagger 문서만으로 연동 시스템을 만들기 어렵다.

 

위와 같은 이유들로 API 문서화를 바꿔야하나 생각하던 중 Spring Rest Docs를 알게 되었고, 적용하면서 삽질한 내용을 공유하려고 합니다. 👀


📚 개념 정리 & 사전준비

1. Swagger vs Spring Rest Docs

 

2. 사전 준비

Spring Rest Docs는 빌드 도구, 테스트, 방식에 따라 사용법이 조금씩 다릅니다. 제가 Spring Rest Docs를 적용한 환경은 아래와 같습니다.

  • 빌드 도구
    • gradle
  • 테스트
    • JUnit4
    • MockMvc
  • Spring Rest Docs 방식
    • asciidoc
  • 의존성 추가
    • spring-boot-starter-web
    • spring-boot-starter-test

💻 구현

1. build.gradle 설정

Spring Rest Docs를 사용하기 위해서는 build.gradle을 설정해주어야합니다. (maven인 경우)

 

build.gradle

plugins {
    id 'org.asciidoctor.convert' version "1.5.3" // (1)
}

ext {
    snippetsDir = file('build/generated-snippets') // (2)
}

asciidoctor { // (3)
    inputs.dir snippetsDir
    dependsOn test
}

dependencies {
    asciidoctor 'org.springframework.restdocs:spring-restdocs-asciidoctor:1.2.6.RELEASE' // (4)
    testCompile group: 'org.springframework.restdocs', name: 'spring-restdocs-mockmvc', version: '2.0.2.RELEASE' // (5)
}

// (6)
//bootJar {
//	dependsOn asciidoctor
//	from ("${asciidoctor.outputDir}/html5") {
//		into "static/docs"
//	}
//}

task copyDocument(type: Copy) { 
    dependsOn asciidoctor

    from file("build/asciidoc/html5/")
    into file("src/main/resources/static/docs")
}

build {
    dependsOn copyDocument
}
  1. Asciidoctor 플러그인을 적용하기 위해 추가해준다.
  2. 생성된 스니펫의 출력 위치를 정의한다. (build/generated-snippets)
  3. 2번에서 정의한 출력위치로 스니펫을 넣어주고, 문서가 작성되기 전에 테스트가 실행되도록 한다.
  4. .adoc 파일에서 빌드, 스니펫 생성을 자동으로 구성되기 위해 추가하는 의존성이다.
  5. restdocs에서 MockMvc를 사용할 때 사용하는 의존성이다.
  6. 자동으로 생성된 html 파일을 static 폴더로 복사해주는 구성이다.
    • 라고 공식문서에 나와있는데 저는 이 구성을 추가해주어도 static에 html에 복사가 안되는 현상이 발생하여 아래 구성으로 대체했습니다.
    • 빌드에서 copyDocumet라는 파일을 복사해주는 task를 실행시켜주는 구성입니다.

 

2. 컨트롤러 생성

HelloController.java

@RestController
public class HelloController {

  @GetMapping("/hello/{name}")
  public ResponseEntity<Person> hello(@PathVariable String name) {
    return ResponseEntity.ok()
        .body(new Person(name, "안녕하세요!"));
  }
}

 

Person.java

public class Person {

  private String name;
  private String message;

  public Person(String name, String message) {
    this.name = name;
    this.message = message;
  }

  public String getName() {
    return name;
  }

  public String getMessage() {
    return message;
  }
}

 

/hello/{name}으로 접근을 하면 아래와 같은 응답을 주는 컨트롤러를 간단하게 구성했습니다.

{
  "name": {name},
  "message": "안녕하세요!"
}

 

3. 테스트 생성

HelloControllerTest.java

@RunWith(SpringRunner.class)
@SpringBootTest
public class HelloControllerTest {

  @Rule
  public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); // (1)

  private MockMvc mockMvc;

  @Autowired
  WebApplicationContext context;


  @Before
  public void setUp() { // (2)
    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
        .apply(documentationConfiguration(this.restDocumentation))
        .build();
  }

  @Test
  public void Hello_테스트() throws Exception {

    // given
    String name = "칩앤데일";

    // when
    mockMvc.perform(get("/hello/" + name)
        .characterEncoding("utf-8")
        .accept(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk())  // then
        .andExpect(jsonPath("$.name").value(name))
        .andDo(document("hello")); // (3)
  }
}
  1. 프로젝트의 빌드 도구(build.gradle)를 기반으로 디렉토리로 자동 구성을 하는 역할을 한다.
    • Maven : target/generated-snippets
    • Gradle : build/generated-snippets
  2. documentationConfiguration()에서 인스턴스를 얻어 MockMovc를 구성한다.
  3. 구성된 디렉토리 아래에 hello라는 디렉토리를 만들어 스니펫을 만든다. (스니펫은 RestDocumentationResultHandler에 의해 작성된다.)

테스트까지 작성을 완료하고 나면, 테스트를 실행해줍니다.

테스트에 성공하면 build - generated-snippets-hello에 총 6개의 기본 스니펫이 생긴 것을 확인할 수 있습니다. 이제 이 스니펫들을 가지고 문서화를 해보도록 하겠습니다.

 

4. .adoc 파일 만들기

src/docs/asciidoc/index.adoc 파일을 만들어줍니다. adoc의 파일 이름은 index가 아닌 다른 것이어도 상관없습니다.

intelliJ라면 AsciiDoc 플러그인을 다운로드 받으면 .adoc 파일에서 편하게 작업이 가능합니다!

 

index.adoc

== 인사하기

=== Request

CURL:
include::{snippets}/hello/curl-request.adoc[]

Request Parameters:
include::{snippets}/hello/http-request.adoc[]

Request HTTP Example:
include::{snippets}/hello/http-request.adoc[]

=== Response
Response:
include::{snippets}/hello/http-response.adoc[]

Asciidoc 기본 사용법은 잘 정리된 글이 있으니 참고하시면 좋을 것 같습니다.

스니펫을 가져오고 싶을 때는 include::{snippets}/경로/curl-request.adoc[]로 가져올 수 있습니다.

 

이제 열심히 작성한 index.adoc을 우리가 직접 볼 수 있게 html로 뽑아내보겠습니다.

 ./gradlew build

를 통해 gradle을 빌드를 해줍니다.

 

intelliJ의 경우 Gradle - build를 통해 빌드할 수 있습니다.

 

빌드가 성공적으로 종료되면 build/asciidoc/html5src/main/resource/static/docs에 index.html이 생긴 것을 확인할 수 있습니다. (만약 src/main/resource/static/docs에 index.html이 생기지 않는다면 build.gradle 설정에서 build를 bootJar (boot 버전이 1.4 이하라면 jar)로 변경해보시길 바랍니다.)

static에 있는 파일은 매핑 컨트롤러가 없어도 경로를 쓰면 접근이 가능하기 때문에, 어플리케이션을 실행하고 localhost:8080/docs/index.html에 접속하면

잘 나오는 것을 확인할 수 있습니다!


✨ 정리

기본 스니펫 6개 이외에도 추가적인 스니펫을 설정할 수 있는데, 그 부분은 다음 포스팅에서 알아보도록 하겠습니다! 저는 개인적으로 Swagger보다 Spring Rest Docs를 사용하는 방법이 더 깔끔하고 마음에 들어서 적용 과정을 포스팅으로 정리해보았습니다.

혹시 글을 읽으면서 잘못된 내용이 있으면 댓글로 알려주시면 감사하겠습니다! 읽어주셔서 감사합니다! 😊


👏 참고