티스토리 뷰
만약 진행 중인 프로젝트가 프론트엔드와 백엔드 개발자가 분리되어 있거나, 타 회사와 협업 중이라면, API 문서는 원활한 협업을 위한 필수 요소다. 하지만 완벽한 API 문서를 만드는 것은 마치 테스트 코드를 작성하는 것과 같다. 없어서는 안 되지만, 범위가 정해져 있는 것도 아니며, 방법도 다양하다. 특히 귀찮다고 생각하기 십상이라 어떻게 하면 신뢰성 있게 만들되 효율적으로 작성할 수 있을까를 고민하게 된다. 개발자라면 사실 코드가 아닌 문서를 작성하는 일이 달갑지만은 않을 것이다.
이런 고민에서 나온 방법이 API 문서 자동화다. 개발 코드에 API 문서 작성을 위한 코드를 자연스럽게 녹여 내서 문서 작업이 아닌 코딩으로 API 문서를 작성할 방법들이 있다.
본 포스팅에서는 특히 Spring 환경에서 테스트를 통해 API 문서 생성을 자동화하는 방법을 다룬다. 이 과정에서 어떻게 좋은 테스트를 작성할 수 있을까, 어떤 도구들을 활용해서 최대한 효율적으로 테스트와 동시에 API 문서를 작성할 수 있을까에 대한 고민을 담았다.
Spring에서 API 문서 생성 방법
Spring 기반 애플리케이션에서는 대표적으로 Swagger와 Spring REST Docs를 사용해 문서를 자동화할 수 있다.
Swagger
Swagger는 OpenAPI 스펙 기반의 API 문서 자동화 도구다. OpenAPI는 RESTful API를 기술하기 위한 표준 사양(Specification)이다. 예전에는 Swagger Specification이라는 이름으로 시작했지만, 현재는 Linux Foundation 산하의 OpenAPI Initiative에서 관리되고 있으며, Swagger는 OpenAPI 스펙을 구현한 여러 도구 중 하나다.
따라서 Swagger는 다양한 언어 및 프레임워크에서 사용되고 있으며, 당연히 Spring도 지원하고 있다. 요즘 사용하고 있는 대표적인 라이브러리는 springdoc이다.
springdoc은 역시 spring 답게 컨트롤러, DTO 등에 애노테이션을 추가하면, 애플리케이션을 실행했을 때 코드 상의 애노테이션을 기반으로 OpenAPI 스펙에 맞는 swagger 문서를 자동 생성해 준다. 이를 Swagger UI를 통해 API 문서를 확인할 수 있다.
장점 1. 애노테이션 기반으로 빠르게 적용 가능하며 사용법이 간단하다.
예를 들어, 다음과 같이 컨트롤러에 애노테이션을 붙이면,
@RestController
@RequestMapping("/hello")
public class HelloController {
@GetMapping
@Operation(summary = "Hello API", description = "이름을 받아 인사말을 반환한다.")
public ResponseResult<HelloData> hello(@RequestParam String name) {
return new ResponseResult<>(new HelloData(name));
}
}
애플리케이션 실행 후 {도메인URL}/swagger-ui/index.html 에 접속하면 자동 생성된 문서를 UI로 확인할 수 있다. 비즈니스 로직을 작성하는 동시에 별다른 설정 없이 빠르게 API 문서까지 작성 가능하기 때문에 빠른 작업과 결과물을 요구하는 프로젝트에 적합할 수 있다.
장점 2. Swagger UI가 꽤 이쁘게 나온다.
공식 문서에서 제공하는 예시를 확인해 보면, 생각보다 깔끔하고 직관적인 UI인 것을 확인할 수 있다.

물론 각자의 기준에 따라서 성에 안찰 수 있지만, 뒤에 설명할 Spring REST Docs와 비교하면, 큰 공을 들이지 않았음에도 이 정도 퀄리티의 UI라면 감사해야 한다.
장점 3. 실제 API 요청, 인증 토큰 등의 테스트를 문서에서 직접 수행할 수 있다.
Swagger UI 보다 어쩌면 더 매력적인 장점이다. 보통 API 문서와는 별개로 Postman 같은 도구를 사용해 HTTP 요청, 인증 토큰 등의 실제 테스트를 수행해야 한다. 하지만 Swaager는 문서 페이지에서 제공되는 테스트를 직접 수행해 볼 수 있기 때문에 클라이언트 입장에서는 이보다 더 좋은 API 문서는 없을 것이다.
단점 1. API 문서 작성을 위한 애노테이션이 비즈니스 로직에 침투한다.
예시 코드를 보면, @Operation 같은 문서 작성을 위한 애노테이션이 비즈니스 로직에 필수적으로 추가 돼야 한다. 이 점이 사실 Swagger의 가장 치명적인 단점이라고 생각한다. 위 예시에서는 하나의 애노테이션 밖에 없지만, 실제로 쓸모있는 API 문서를 위해서는 여러 개의 애노테이션과 부가 설명이 덕지덕지 붙게 된다.
@RestController
@RequestMapping("/api/users")
@Tag(name = "User API", description = "사용자 관련 API")
public class UserController {
@Operation(
summary = "사용자 정보 조회",
description = "ID를 이용하여 특정 사용자 정보를 조회한다.",
responses = {
@ApiResponse(responseCode = "200", description = "정상 응답",
content = @Content(schema = @Schema(implementation = UserResponse.class))),
@ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음")
}
)
@GetMapping("/{id}")
public ResponseEntity<UserResponse> getUser(
@Parameter(description = "사용자 ID", example = "1") @PathVariable Long id
) {
UserResponse response = new UserResponse(id, "crack", "crack@example.com");
return ResponseEntity.ok(response);
}
// ...
위 예시처럼, API 문서를 위한 코드가 실제 비즈니스 로직보다 많은 지경에 이른다. 딱 봐도 코드 가독성이 너무 떨어지고, 유지보수에 있어서도 API 관련 작업을 수정하는데 비즈니스 로직이 포함된 파일을 건드려야 하기 때문에 최악이다.
깔끔한 비즈니스 로직을 위해 항상 이런저런 아키텍처를 고민하는 대부분의 백엔드 개발자들이 이런 부분을 보고 인상을 찌푸리지 않을 수 없을 것이다. 필자도 이 부분이 가장 마음에 걸려 Spring REST Docs를 선호하게 됐다.
단점 2. 문서 형식이 제한적이다.
이 부분은 상황에 따라서 장점이 될 수 있겠지만, 그래도 Swagger UI를 벗어날 수 없다는 점과 애노테이션 기반의 자동 문서 생성이기 때문에 아무래도 형식이 상당히 제한적이다.
Spring REST Docs
Spring REST Docs는 Spring 공식에서 추천하는 방법으로, 테스트를 기반해서 문서를 생성한다.
즉, 실제 테스트를 통과한 요청/응답 내용을 토대로 정적인 HTML 문서로 만드는 방식이다. 문서화된 내용은 실제 API의 결과와 정확히 일치하기 때문에 높은 신뢰성을 보장한다. 테스트 코드는 거의 필수로 작성해야 되기 때문에, 겸사겸사 API 문서를 작성한다고 생각하면 괜찮다.
Spring REST Docs 공식 문서를 확인해 보면, 테스트는 총 3가지 방법으로 작성돼야 한다.
- Spring MVC’s test framework (MockMVC)
- Spring WebFlux’s
WebTestClient - REST Assured 5
이 중, 가장 많이 사용되면서 쉬운 방식이 MockMVC일 것이다. 공식 문서에서 가장 먼저 소개되기도 하고, MockMVC를 사용하면 애플리케이션 전체를 띄우지 않고도 간단한 API 테스트가 가능하기 때문에 많이 선호된다. 하지만 필자는 REST Assured 5 방식을 선택했는데, 그 이유는 아래에서 설명하겠다.
Spring REST Docs 라이브러리를 사용하기 위한 최소 스펙은 다음과 같다.
- Java 17
- Spring Framework 6
- REST Assured 5.2 (
spring-restdocs-restassured를 사용한다면)
작동 방식
작동 방식은 Swagger에 비해 다소 복잡한 감이 있다.

- restdocs 라이브러리를 사용한 테스트 케이스 실행
- 테스트 산출물로 .adoc 파일들이
build/generated-snippets경로에 저장- 이 스니펫 조각들은 실행된 테스트를 참고하여 작성된 http request, response 명세같은 역할
src/docs/asciidoc경로에 위/build/generate-snippets/*.adoc파일들을include해서AsciiDoc 스타일로 문서 작성./gradlew asciidoctor명령어로 Asciidoctor Task를 실행하면 adoc 파일들을 엮어서 html 문서를 생성
Asciidoc : Markdown같은 텍스트 기반 문서 작성 형식으로 Asciidoctor 프로세서가 HTML 5나 PDF로 변환해 줌 (Asciidoc, Asciidoctor)
예를 들어, 다음과 같이 테스트를 작성하면:
@Test
void helloTest() {
RestAssured.given(spec)
.queryParam("name", "crack")
.filter(document("hello",
queryParameters(parameterWithName("name").description("이름")),
responseFields(
fieldWithPath("data.name").description("요청한 이름"),
fieldWithPath("data.greeting").description("인사말")
)
))
.when().get("/hello")
.then().statusCode(200);
}
테스트 성공 시 build/generated-snippets/hello 경로에 .adoc 스니펫 파일이 생성된다. 이후 Asciidoctor Task 통해 HTML 문서로 변환할 수 있다.

장점 1. 테스트가 통과한 API만 문서화되므로 신뢰성이 높다.
API 문서를 생성하기 위해 항상 테스트를 작성하고, 통과해야 한다는 강제성이 이 문서의 신뢰성을 상당히 높여줄 수 있다. 만약 API 스펙이 변경되더라도, 다시 테스트를 작성하고 통과해야 스니펫 조각들을 생성하고 변환할 수 있다. 아무래도 API 문서의 가장 중요한 부분이 신뢰성이다 보니, 오히려 이런 강제성이 Spring REST Docs를 강력한 도구로 만들었다고 생각한다.
장점 2. 문서 작성을 비즈니스 로직과 분리할 수 있다.
Swagger와 가장 크게 비교되는 부분 중 하나다. API 문서 작성을 위한 코드를 비즈니스 로직이 아닌 테스트 로직에 작성하기 때문에 깔끔한 비즈니스 로직을 유지할 수 있다. 개인적으로 이 부분이 가장 마음에 들었다.
장점 3. 자유로운 커스텀이 가능하다.
보통 이쁘게 꾸미는 것에 재능이 없는 백엔드 개발자들에게 장점인가 싶긴 하지만, 조금만 익숙해지면 생각보다는? 괜찮다고 느껴진다. 그리고 요즘은 사실 AI가 초안을 잘 만들어 주기 때문에 무조건 장점이다.
단점 1. 초기 설정이 복잡하고 진입장벽이 높다.
아래에 더 자세히 설명하겠지만, Swagger에 비해 초기 설정할 부분이 많고, 알아야 할 사전 지식들도 꽤 있다.
예를 들어, build.gradle에서 task 설정이 필요하고, Asciidoc 문서 스타일도 학습이 필요하다. restdoc 라이브러리도 학습해야 하는데, 테스트 작성이 필수라 MockMVC나 RestAssured 같은 테스트 도구도 다룰 줄 알아야 한다. 만약 프로젝트 중간에 Spring REST Docs를 도입하려고 하는데, 이미 작성된 테스트들이 restdoc에서 지원하지 않는 도구들로 작성되어 있다고 생각해 보자. 어지럽다.
한 마디로, 이런 것들이 초기 진입 장벽을 높일 수밖에 없고, 빠르게 API 문서를 산출해야 할 때에는 꽤 무겁게 느껴진다.
단점 2. Swagger처럼 인터랙티브한 테스트는 불가능하다.
단순 정적 HTML이기 때문에 API 문서 내에서 Swagger처럼 HTTP 요청같은 테스트가 불가능하다. 때문에 별도의 Postman 같은 테스트 도구같은게 필요하다는 점이 다소 불편하게 느껴진다.
Swagger vs Spring REST Docs
| 항목 | Swagger | Spring REST Docs |
| 문서 생성 기준 | 코드 애노테이션 기반 | 테스트 결과 기반 |
| 문서 UI 제공 | O (Swagger UI 제공) | X (정적 HTML 제공) |
| 테스트 연계 | 테스트와 분리 | 테스트 결과에서 추출 |
| 문서 신뢰도 | 동기화 누락 시 신뢰도 하락 가능 | 테스트 통과 결과만 문서화 → 신뢰도 높음 |
| 적용 난이도 | 낮음 | 높음 |
| 커스터마이징 | UI 제한적 조작만 가능 | Asciidoctor 기반 → 매우 유연함 |
Swagger는 빠른 적용과 쉬운 사용을 강점으로 하지만, 비즈니스 로직이 더럽혀 질 수 있다는 단점이 있다. 반면, Spring REST Docs는 정확하고 신뢰도 높은 문서를 제공하지만, 그 만큼 설정과 유지에 더 많은 노력이 필요하다.
협업 시 문서의 정확성과 신뢰도가 중요하다면 REST Docs가 적합하고, 빠르게 API 테스트와 공유가 필요하다면 Swagger가 편리하다. 프로젝트 성격과 팀 역량에 따라 선택하는 것이 중요하다.
Spring REST Docs에 왜 RestAssured를 선택했나?
앞서 언급했듯이 필자는 Spring REST Docs와 함께 테스트 도구로 RestAssured를 선택했다. 왜 MockMVC가 아닌 RestAssured를 선택하게 됐는지에 대한 배경과 개인적인 생각을 얘기해 보겠다.
API 문서는 자주 바뀌면 안 된다.
Spring REST Docs는 문서 생성을 위한 테스트가 전제된다. 이때 어떤 테스트 도구를 쓸 것인가가 중요하다. 사실 Spring에서 사용할 수 있는 테스트 도구는 기본적인 Junit과 AssertJ를 기저에 두고, 목적에 따라 다양한 도구들을 추가할 수 있다.
MockMVC 는 Spring의 DispatcherServlet을 모킹(mocking)해서 HTTP 요청/응답 테스트를 수행하는 도구다. 가장 큰 특징은 스프링 부트를 띄우지 않고 테스트할 수 있기 때문에 가볍고 빠르게 테스트 할 수 있다. 보통 Mockito와 함께 사용되는데, 테스트 하고 싶은 대상 클래스가 의존하고 있는 인스턴스들을 Mock 객체로 만들어 독립적인 테스트를 가능하게 해준다. 즉, MockMVC는 단위 테스트와 결이 잘 맞다.
하지만 단위 테스트는 변경 사항이 발생하기 쉽다. 단위 테스트를 하는 장점은 쉽고 빠르게 다양한 로직들을 점검할 수 있다는 점이다. 바꿔 말하면, 언제든지 테스트 추가 및 변경이 쉽다는 것이다. 프론트엔드 개발자 또는 외부 업체와 협업하는데 API 문서 스펙이 자주 변경된다면 혼란을 초래할 수 있다.
💡 사실 단위 테스트라고 해서 항상 Mock 객체를 사용하는 것은 아니다. 모킹(Mocking) 여부는 의존성 객체가 애플리케이션 내부에서 관리되는 객체인지, 아닌지가 중요하다. 전자의 경우는 프로젝트 팀 내부에서 관리되는, 흔히 말하는 Service나 Repository같은 객체들을 의미하며, 더 넓게는 데이터베이스까지도 포함된다.이런 객체들을 전부 Mock 객체로 만들게 되면 깨지기 쉬운 테스트가 된다.예를 들어, Repository의 필드 이름을 바꾼다든지, 타입을 바꿨다고 해보자. 해당 Repository를 사용하는 테스트에서 Mock 객체로 만들어 놓았다면, 이 테스트는 바로 깨지게 된다. 이처럼 Mock 객체를 만들 때는 세부 요구사항까지 반영되기 때문에 깨지기 쉬운 테스트가 되어 버린다. 만약, 이런 것들을 놓치더라도 테스트는 성공하게 되는데, 사실 이게 더 큰 문제가 될 수 있다.
데이터베이스 역시 Mock 객체로 만들기 보다는 도커를 사용한 별도의 테스트 DB나 인메모리 DB를 사용하는 편이 낫다.
가독성 좋은 BDD 스타일 코드 작성이 가능하다.
BDD(Behavior Driven Development) 는 애플리케이션의 행위를 기반으로 하는 테스트 방법이다. 테스트 과정이 given, when, then 으로 이루어지는데, 이런 자연어 스타일 시나리오가 가독성 좋게 느껴진다.
RestAssured BDD 스타일 예시를 간단하게 확인해 보면 다음과 같다.
RestAssured
.given()
.contentType("application/json")
.queryParam("name", "crack")
.when()
.get("/hello")
.then()
.statusCode(200)
.body("data.name", equalTo("crack"))
.body("data.greeting", equalTo("Hello, crack"));
사용 방법을 잘 모르더라도, 이 테스트가 주어진 재료가 어떤 것이고, 어떤 상황에서, 무엇을 검증하려고 하는지가 한눈에 들어온다.
또한, AssertJ와의 연계도 쉽다. AssertJ는 테스트 결과 값을 직관적이고 유연하게 검증할 수 있기 때문에 최근 자주 사용되는 라이브러리로 Spring Boot에도 포함되어 있다.
RestAssured는 응답을 Response 객체로 추출 가능하다.
Response response = RestAssured
.given()
.when()
.get("/hello");
String name = response.jsonPath().getString("data.name");
assertThat(name).isEqualTo("crack");
이런 식으로 검증을 하면, 검증 로직이 분리되어 좀 더 가독성 좋고 유연한 코드 작성이 가능하다.
테스트 예시 작성해 보기
마지막으로 간단하게 Spring REST Docs + RestAssured를 사용한 테스트 및 API 문서 생성 예시를 작성해 보자.
환경 세팅
먼저 Spring REST Docs 공식 문서에서 제시하는 build.gradle 세팅 방법을 참고하면 좋다. 이를 바탕으로 배포 환경을 추가했고, 내용은 다음과 같다.
1. 플러그인 등록
id 'org.asciidoctor.jvm.convert' version '3.3.2'
- Asciidoctor를 사용하기 위한 Gradle 플러그인.
2. 의존성 설정
testImplementation 'org.springframework.restdocs:spring-restdocs-core'
testImplementation 'org.springframework.restdocs:spring-restdocs-restassured'
testImplementation 'io.rest-assured:rest-assured:5.4.0'
- spring-restdocs-core : Spring REST Docs의 핵심 라이브러리
- spring-restdocs-restassured : REST Docs를 RestAssured와 연동하기 위한 어댑터 라이브러리
- rest-assured : RestAssured 라이브러리로 5.2 버전 이상이 필요
3. 문서 스니펫 디렉토리 설정
ext {
snippetsDir = file('build/generated-snippets')
snippetsJarDir = file("src/docs/asciidoc/snippets")
}
- snippetsDir : 테스트 실행 시 생성될
.adoc스니펫 파일들의 출력 디렉토리. ****REST Docs는 테스트 결과로 이 경로에 요청/응답 명세 조각들을 저장 - snippetsJarDir : 위에서 생성된 스니펫들을 문서로 만들기 위해 복사해둘 디렉토리
4. 스니펫 복사 Task
tasks.register('copySnippets', Copy) {
from snippetsDir
into snippetsJarDir
}
- 테스트 실행으로
build/generated-snippets에 생성된 스니펫들을src/docs/asciidoc/snippets로 복사 - asciidoctor에서 해당
.adoc조각들을 include 하기 위해 필요
5. Asciidoctor Task 설정
asciidoctor {
inputs.dir snippetsDir
dependsOn test
attributes 'snippets': snippetsDir
}
- inputs.dir snippetsDir : 이 Task는 snippetsDir의 내용을 입력 파일로 사용한다는 의미입니다.
- dependsOn test : HTML 문서를 만들기 전에 테스트가 반드시 실행돼야 하기 때문에, 테스트 실행 → 스니펫 생성 → HTML 문서 생성 흐름 보장.
- attributes 'snippets': snippetsDir :
.adoc문서 내에서 snippets 라는 속성으로 이 경로를 참조할 수 있도록 설정. ex)include::{snippets}/hello/response-fields.adoc[]
6. bootJar Task 설정
bootJar {
dependsOn asciidoctor
from("${asciidoctor.outputDir}") {
into 'static/docs'
}
}
- dependsOn asciidoctor : JAR 패키징 전에 asciidoctor가 먼저 실행되도록 설정
- from("${asciidoctor.outputDir}") : 생성된 HTML 문서를 JAR에 포함
- into 'static/docs' : 결과 문서를 /docs/index.html 등의 경로에서 열어볼 수 있게 Spring Boot 리소스(static)에 포함
아래는 전체 build.gradle 파일 내용
plugins {
id 'java'
id 'org.springframework.boot' version '3.5.0'
id 'io.spring.dependency-management' version '1.1.7'
id 'org.asciidoctor.jvm.convert' version '3.3.2'
}
group = 'we-are-crack'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.restdocs:spring-restdocs-core'
testImplementation 'org.springframework.restdocs:spring-restdocs-restassured'
testImplementation 'io.rest-assured:rest-assured:5.4.0'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
ext {
snippetsDir = file('build/generated-snippets')
snippetsJarDir = file("src/docs/asciidoc/snippets")
}
tasks.named('test') {
useJUnitPlatform()
}
tasks.register('copySnippets', Copy) {
from snippetsDir
into snippetsJarDir
}
asciidoctor {
inputs.dir snippetsDir
dependsOn test
attributes 'snippets': snippetsDir
}
bootJar {
dependsOn asciidoctor
from("${asciidoctor.outputDir}") {
into 'static/docs'
}
}
테스트 코드
Spring REST Docs 문서를 생성하기 위해 작서앻야 하는 파일은 크게 총 3종류다.
- 테스트 대상이 되는 클래스
- 테스트 파일
- adoc 파일 (Asciidoctor에 의해 HTML로 변환)
테스트 대상 클래스(API 컨트롤러)
먼저 간단한 API 컨트롤러를 하나 작성했다.
HelloApiController
@RestController
@RequestMapping("/hello")
public class HelloApiController {
@ResponseStatus(HttpStatus.OK)
@GetMapping
public ResponseResult<HelloData> hello(@RequestParam String name) {
HelloData helloData = new HelloData(name);
return new ResponseResult<>(helloData);
}
@ResponseStatus(HttpStatus.OK)
@PostMapping
public ResponseResult<HelloData> helloV2(@RequestBody HelloRequest request) {
HelloData helloData = new HelloData(request.getName());
return new ResponseResult<>(helloData);
}
@Getter
@NoArgsConstructor
@AllArgsConstructor
static class HelloRequest {
private String name;
}
@Data
@AllArgsConstructor
static class ResponseResult<T> {
private T data;
}
@Getter
static class HelloData {
private final String name;
private final String greeting;
public HelloData(String name) {
this.name = name;
this.greeting = "Hello, " + name;
}
}
}
간단하게 요약해 보면,
- GET : 쿼리 파라미터로
name을 받아HelloData객체 응답 - POST : request-body에
name을 받아HelloData객체 응답
테스트 파일
이를 위한 테스트 코드는 다음과 같다. (예시 코드라 급하게 작성하다 보니, 중복되는 부분이 많아 리팩토링이 필요하다…)
HelloApiControllerTest
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ExtendWith(RestDocumentationExtension.class)
class HelloApiControllerTest {
@LocalServerPort
private int port;
private Filter restDocsConfig; // 공통 config 필터
@BeforeEach
void setUp(RestDocumentationContextProvider restDocs) {
// 공통 config 필터 (snippets 경로 등 설정)
this.restDocsConfig = RestAssuredRestDocumentation.documentationConfiguration(restDocs);
}
/**
* 재사용 가능한 RequestSpecification 생성
*/
private RequestSpecification createSpecWithDocs(RestDocumentationFilter snippetFilter) {
return new RequestSpecBuilder()
.setPort(port)
.addFilter(restDocsConfig)
.addFilter(snippetFilter) // 문서화용 필터만 테스트마다 다르게 주입
.build();
}
/**
* 재사용 가능한 문서 필터 생성
*/
private RestDocumentationFilter createHelloDocFilter(String identifier) {
return document(identifier,
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
queryParameters(
parameterWithName("name").description("이름")
),
responseFields(
fieldWithPath("data.name").description("요청한 이름"),
fieldWithPath("data.greeting").description("인사말")
)
);
}
private RestDocumentationFilter createHelloV2DocFilter(String identifier) {
return document(identifier,
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
requestFields(
fieldWithPath("name").description("이름")
),
responseFields(
fieldWithPath("data.name").description("요청한 이름"),
fieldWithPath("data.greeting").description("인사말")
)
);
}
@Test
@DisplayName("GET /hello API - name 파라미터를 받아서 응답을 반환한다.")
void 문서화테스트() {
RequestSpecification spec = createSpecWithDocs(createHelloDocFilter("hello-get"));
Response response = RestAssured
.given(spec)
.queryParam("name", "crack")
.when()
.get("/hello");
String name = response.jsonPath().getString("data.name");
String greeting = response.jsonPath().getString("data.greeting");
assertThat(response.getStatusCode()).isEqualTo(200);
assertThat(name).isEqualTo("crack");
assertThat(greeting).isEqualTo("Hello, crack");
}
@Test
@DisplayName("GET /hello API - request body 에 name 필드를 받아서 응답한다.")
void 문서화테스트_V2() {
RequestSpecification spec = createSpecWithDocs(createHelloV2DocFilter("hello-post"));
String requestBody = """
{
"name" : "crack"
}
""";
Response response = RestAssured
.given(spec)
.contentType(ContentType.JSON)
.body(requestBody)
.when()
.post("/hello");
String name = response.jsonPath().getString("data.name");
String greeting = response.jsonPath().getString("data.greeting");
assertThat(response.getStatusCode()).isEqualTo(200);
assertThat(name).isEqualTo("crack");
assertThat(greeting).isEqualTo("Hello, crack");
}
}
문서를 만드는데 중요한 부분만 골라서 정리하면 다음과 같다.
@SpringBootTest + @ExtendWith(RestDocumentationExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ExtendWith(RestDocumentationExtension.class)
SpringBootTest: Spring Boot를 실제 메모리 올려서 테스트. 이때RANDOM_PORT를 써서 임시 포트로 사용RestDocumentationExtension: Spring REST Docs와 JUnit5를 연동하기 위한 Extension으로@BeforeEach에서RestDocumentationContextProvider를 주입받기 위해 필요
@beforeEach 설정 - 공통 설정 필터 구성
private Filter restDocsConfig;
@BeforeEach
void setUp(RestDocumentationContextProvider restDocs) {
this.restDocsConfig = RestAssuredRestDocumentation.documentationConfiguration(restDocs);
}
Filter: RestAssured에서 요청 또는 응답을 가로채 세션, 인증, 문서화 등을 구현하는데 사용되는 인터페이스(마치 AOP의 around advice)RestDocumentationContextProvider는 Spring REST Docs에서 문서 생성에 필요한 컨텍스트 정보(RestDocumentationContext)를 넘겨주는 객체로, 해당 예시에서는 RestAssured에게 넘겨주게 됨documentationConfiguration()에 위 provider(restDocs)를 넘겨주면, Spring REST Docs의 설정 정보를 RestAssured가 사용할 수 있도록RestAssuredRestDocumentationConfigurer(Filter구현체)를 반환.
RestDocumentationContext는 Spring REST Docs에서 테스트 실행 중에 문서 생성에 필요한 구체적인 설정값들을 담고 있는 객체다. 주요 정보들은 다음과 같다.outputDirectory: 문서 스니펫이 저장될 디렉토리 경로identifier: 각 테스트마다 고유하게 붙는 문서 식별자configuration: 문서 생성 시 사용하는 공통 설정들testClass,testMethod: 현재 실행중인 테스트의 클래스와 메서드 이름
문서 필터 정의 메서드
private RestDocumentationFilter createHelloDocFilter(String identifier) { ... }
document(...): 어떤 문서들을 정보화할지 작성하는 팩토리 메서드로RestDocumentationFilter를 반환RestDocumentationFilter:Filter인터페이스 구현체로, RESTful API 문서화를 위한 필터preprocessRequest(prettyPrint()),preprocessResponse(prettyPrint()): 요청/응답 본문을 보기 좋게 정리해서 문서에 출력queryParameters(...): 쿼리 파라미터 필드 설명requestFields(...): request body에 포함될 필드 설명responseFields(...): response body에 포함될 필드 설명
(추가로 테스트마다 고유한 식별자(identifier)를 받아서 각 테스트 실행 후 생성되는 스니펫들의 경로가 분리되도록 했다)
RequestSpecification 조립 메서드
private RequestSpecification createSpecWithDocs(RestDocumentationFilter snippetFilter)
앞서 설명했던 문서화를 위한 공통 설정 정보들과 테스트 별 문서 필터들을 조립해 RestAssured 용 요청 사양(RequestSpecification)을 생성하는 메서드다. 파라티머로 받는 RestDocumentationFilter 는 각 테스트에 맞게 호출될 문서 필터 정의 메서드들(createHelloDocFilter, createHelloV2DocFilter)의 호출 결과다.
addFilter(restDocsConfig): 공통 설정 필터 추가addFilter(snippetFilter): 테스트에 맞게 구성된 문서화 필터 추가
RestAssured를 활용한 테스트 코드
@Test
@DisplayName("GET /hello API - name 파라미터를 받아서 응답을 반환한다.")
void 문서화테스트() {
RequestSpecification spec = createSpecWithDocs(createHelloDocFilter("hello-get"));
Response response = RestAssured
.given(spec)
.header("Host", "api.baeuja.xyz")
.queryParam("name", "crack")
.when()
.get("/hello");
assertThat(response.getStatusCode()).isEqualTo(200);
String name = response.jsonPath().getString("data.name");
String greeting = response.jsonPath().getString("data.greeting");
assertThat(name).isEqualTo("crack");
assertThat(greeting).isEqualTo("Hello, crack");
}
RestAssured.given(spec): RestAssured 요청 사양(RequestSpecification) 객체를 생성한 뒤, RestAssured 요청 준비 메서드에 넣어 준다.
테스트 실행
이제 테스트를 실행하면, build/generated-snippets/{identifier}/ 경로에 스니펫 조각들이 생성된다. 당연히 테스트가 성공해야 한다.

Adoc 문서 작성 후 Asciidoctor Task 실행
이제 이 스니펫 조각들을 가지고 src/docs/asciidoc/ 경로에 adoc 파일로 api 문서를 작성하면 된다. 필요한 HTTP 스펙들은 스니펫들을 include 해서 넣어주면 된다.
index.adoc
= Hello API 문서
:toc: left
:toclevels: 2
:source-highlighter: highlightjs
:snippets: {projectdir}/build/generated-snippets
== GET /hello (쿼리 파라미터)
=== 요청
include::{snippets}/hello-get/http-request.adoc[]
=== 응답
include::{snippets}/hello-get/http-response.adoc[]
=== 요청 파라미터
include::{snippets}/hello-get/query-parameters.adoc[]
=== 응답 필드
include::{snippets}/hello-get/response-fields.adoc[]
== POST /hello (JSON Request Body)
=== 요청
include::{snippets}/hello-post/http-request.adoc[]
=== 응답
include::{snippets}/hello-post/http-response.adoc[]
=== 요청 필드
include::{snippets}/hello-post/request-fields.adoc[]
=== 응답 필드
include::{snippets}/hello-post/response-fields.adoc[]
터미널에서 아래 명령어로 Asciidoctor Task 실행
./gradlew asciidoctor

만약 API 문서를 외부에 서빙하고 싶다면 jar 파일로 빌드하면 된다.
./gradlew bootJar
java -jar app.jar
최종 생성된 문서는 {도메인}/docs/index.html 경로에서 확인할 수 있다.

Spring REST Docs는 진입장벽이 높지만, 한 번 구조를 잡아두면 신뢰도 높은 문서 작성이 가능하다. 특히 테스트 기반으로 동작하기 때문에 API 변경이 생기면 바로 문서에도 반영된다. 테스트와 문서를 동시에 관리하고 싶은 팀이라면 고려해볼 만한 선택이다.
References
'Spring&Spring Boot' 카테고리의 다른 글
| [Spring] 필터(Filter), 인터셉터(Interceptor) (0) | 2025.01.23 |
|---|---|
| [Spring/Spring Boot] Validation을 위한 @Validated 사용 방법 및 원리 이해하기(Bean Validation, BindingResult) (1) | 2024.12.16 |
| [Spring] 타임리프(Thymeleaf) 5가지 기본 표현식/자주 쓰는 구문 정리 (1) | 2024.12.03 |
| [Spring] 코드로 분석해보는 Spring MVC 구조 이해(Front Controller 패턴, DispatchServet) (2) | 2024.11.08 |
| [Spring] Servlet에서 JSP까지, 그리고 한계 (feat. MVC 패턴) (7) | 2024.10.09 |
- Total
- Today
- Yesterday
- Do it! 정직하게 코딩하며 배우는 딥러닝 입문
- 스프링 테스트
- 파이썬 for Beginner 솔루션
- 쉘 코드
- git
- jsp
- 방명록 프로젝트
- 운영체제 반효경
- Spring
- fetch join
- Spring Data JPA
- JPA
- 프로그래머스
- git branch
- 스프링 mvc
- 파이썬 for Beginner 연습문제
- Gradle
- Computer_Networking_A_Top-Down_Approach
- 스프링
- 패킷 스위칭
- git merge
- 선형 회귀
- Spring Boot
- 생활코딩 javascript
- Thymeleaf
- 쉽게 배우는 운영체제
- 스프링 컨테이너
- 지옥에서 온 git
- 김영환
- Python Cookbook
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
