일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- ExceptionHandlerFilter
- ExecutorService
- asciidoctor
- 멀티태스킹
- 비동기
- OAuth2.0
- 멀티스레드
- 도메인 주도 설계(DDD) 기반 마이크로서비스(MSA) 모델링
- FeignClients
- 네이버클라우드 서버
- 멀티프로세싱
- OpenFeign
- Spring Security
- Apple 로그인
- 오블완
- 코드로 배우는 스프링 부트 웹 프로젝트
- springboot
- 비사이드프로젝트
- spring boot
- 스레드
- 티스토리챌린지
- 2024년 상반기 회고
- microsoft
- REDIS
- querydsl
- 프로세스
- JWT
- Spring Cloud OpenFeign
- 사이드 프로젝트
- Spring Reactive Programming
- Today
- Total
기록하기
REST Docs 설정 정리 본문
회사에서 매번 REST Docs 를 설정할 때마다 설정 방법이 헷갈려서 레퍼런스를 많이 찾게 된다. 이미 좋은 자료들이 많긴 하지만 프로젝트마다 그리고 회사의 환경마다 조금씩 설정을 변경해줘야하기 때문에 어려움이 있다.
특히 CI/CD 를 적용해서 젠킨스에 배포를 할 때면 배포 환경에서 MySQL 등에 접속을 막아두었기 때문에 build 시 asciidoctor 가 수행이 되면 안 된다!(이 부분이 제일 힘들었다.. gradle 작성방법이나 문법도 더 공부를 해야겠다..)
그래서 다른 블로그에서 참고한 내용에 좀 더 수정이 필요했기에 해당 내용을 추후 기억하기 위해서라도 적어보려고 한다.
프로젝트 환경
- gradle 7.6
- Spring Boot 2.7.5
- Java 11
build.gradle 에 REST Docs 관련 설정
plugins {
id 'org.asciidoctor.jvm.convert' version '3.3.2'' //1)
}
group = 'com.example'
version = '0.0.1' + '-' + new Date().format('yyyyMMdd-HHmmss')
sourceCompatibility = '11'
ext {
displayVersion = '0.0.1'
buildTime = getBuildDateTime()
//2)
snippetsDir = file('build/generated-snippets')
docsDir = file('src/docs/asciidoc')
htmlDir = file('build/docs/asciidoc')
staticDir = file('src/main/resources/static/docs')
}
def getBuildDateTime() {
return new Date().format('yyMMdd-HHmm')
}
configurations {
//3)
asciidoctorExt
}
repositories {
mavenCentral()
}
dependencies {
//4)
//rest-docs
asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
delete snippetsDir //5)
}
//########################## asciidoctor 설정 ########################## //
asciidoctor {
//6)
attributes "date": new Date().format("yyyy-MM-dd"),
"version": "1.0",
"developer": "jjung_dev"
inputs.dir snippetsDir
dependsOn test
}
asciidoctor.doFirst {
//7)
println "===== start asciidoctor"
delete htmlDir
delete staticDir
}
asciidoctor.doLast {
//8)
println "===== finish asciidoctor"
task copyDocument() {
dependsOn asciidoctor
copy {
from htmlDir
into staticDir
}
}
}
- 1) asciidoctor 플러그인 적용
- 2) snippets, html 파일이 저장될 위치를 선언
- 3), 4) 의존성 설정 및 설정 명시
- 5) 테스트 수행시 기존 snippets 삭제
- 6) 테스트 수행 결과의 snippets 이 저장될 위치 설정
- 7) asciidoctor 실행 전 기존에 생성된 문서 삭제
- 8) asciidoctor 실행 이후 staticDir 로 문서 이동
설정은 여러 블로그 및 공식 문서를 참고하여 설정을 했고, 해당 내용은 아래 참고사항에 링크를 걸어두었다.
여기서 하나 조금 다른 설정은, build 나 bootJar 수행 시 asciidoctor 가 같이 수행되지 않도록 설정을 했다. 같이 실행하게 되면 배포 환경에서 계속 문제가 생겼기 때문에 이렇게 설정했는데 만약 같이 수행이 되어야 한다면 asciidoctor.doLast 에서 copyDocument 를 수행하는 것이 아닌 가장 하단에 아래 내용을 적어줌으로써 bootJar 시 수행되도록 처리할 수 있다.(아니면 다른 방법이 있다면 댓글에 공유해주셔도 됩니다!)
bootJar {
dependsOn asciidoctor
from (htmlDir) {
into staticDir
}
}
Config 및 CustomSnippet 설정
이제 gradle 설정은 끝났고 Test 수행 시 설정만 더해주면 된다.
프로젝트마다 그리고 사람마다 조금씩 다르겠지만 필자의 경우에는 간단하게만 설정을 해주었다.
1. RestDocsConfig
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
@TestConfiguration
public class RestDocsConfig {
@Bean
public RestDocsMockMvcConfigurationCustomizer restDocsMockMvcConfigurationCustomizer() {
return configurer -> configurer.operationPreprocessors()
.withRequestDefaults(prettyPrint())
.withResponseDefaults(prettyPrint());
}
}
이는 TestConfiguration 클래스로, request 나 response 결과를 문서로 보여줄 때 prettyPrint 를 수행하도록 명시해주는 설정 파일이다. 다른 블로그에서는 Util 파일로 만들어서 적용할 테스트에 직접 명시해주기도 하는 것 같다. 혹시 모르니 이 방법도 첨부를 하자면,
먼저, Util 파일을 만들고 테스트 수행하는 곳에 import 를 해주면 된다.
import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.*;
public interface ApiDocumentUtils {
static OperationRequestPreprocessor getDocumentRequest() {
return preprocessRequest(
modifyUris().scheme("https").host("localhost").port(8081),
prettyPrint()
);
}
static OperationResponsePreprocessor getDocumentResponse() {
return preprocessResponse(prettyPrint());
}
}
2. CustomResponseFieldsSnippet
이 설정은 API 호출 결과인 response 를 보여줄 때 custom 하여 필드를 보여주고 싶어 설정을 추가한 내용이다.
데이터 format 이나 description 은 문서화를 위해 좀 더 상세하게 적을 필요가 있다고 생각해 필드를 추가했고 custom 한 설정을 추가했을 때 필요한 설정은 아래와 같다.
- CustomResponseFieldsSnippet
import org.springframework.http.MediaType;
import org.springframework.restdocs.operation.Operation;
import org.springframework.restdocs.payload.AbstractFieldsSnippet;
import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.restdocs.payload.PayloadSubsectionExtractor;
import java.io.IOException;
import java.util.List;
import java.util.Map;
public class CustomResponseFieldsSnippet extends AbstractFieldsSnippet {
public CustomResponseFieldsSnippet(String type, PayloadSubsectionExtractor<?> subsectionExtractor,
List<FieldDescriptor> descriptors, Map<String, Object> attributes,
boolean ignoreUndocumentedFields) {
super(type, descriptors, attributes, ignoreUndocumentedFields, subsectionExtractor);
}
@Override
protected MediaType getContentType(Operation operation) {
return operation.getResponse().getHeaders().getContentType();
}
@Override
protected byte[] getContent(Operation operation) throws IOException {
return operation.getResponse().getContent();
}
}
- /src/test/resources/org/springframework/restdocs/templates/response-fields.snippet
|===
|필드명|타입|필수여부|양식|설명
{{#fields}}
|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}}
|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}}
|{{#tableCellContent}}{{^optional}}true{{/optional}}{{/tableCellContent}}
|{{#tableCellContent}}`+{{format}}+`{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
{{/fields}}
|===
참고로 아실 수도 있겠지만 해당 디렉토리에 설정을 해야하는 이유는 기본적으로 설정되어 있는 templates 의 디렉토리를 따라서 설정을 해줘야 custom 설정이 적용이 되기 때문이다. 따라서 디렉토리 생성이 필요하며 이렇게 설정을 했을 경우 원하는 response 내용을 담을 수 있다.
Test 코드 작성 및 결과
테스트 코드를 작성한 뒤 그 결과를 한 번 살펴보겠다.
테스트는 간단하게 프로젝트 버전을 체크하는 Controller 로 수행을 해보았으며 필자의 경우에는 ControllerTest 라는 추상 클래스를 생성해서 공통적으로 사용하는 설정을 이 곳에서 관리하도록 설계했다. 이 역시 프로젝트마다 조금씩 다르겠지만 지금까지는 이 방법으로 진행을 해왔다.
- ControllerTest
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zum.target.internal.api.domain.TargetDataset;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureRestDocs(uriPort = 8081)
@ActiveProfiles("test")
public abstract class ControllerTest {
@Autowired
protected MockMvc mockMvc;
@Autowired
protected ObjectMapper objectMapper;
}
이외에도 같이 사용하는 변수나 클래스를 이 곳에서 관리해주면 다른 테스트에서 BeforeEach 와 같은 설정을 통해 관리를 할 수 있기 때문에 편리한 부분이 있다.
- StatusControllerTest
class StatusControllerTest extends ControllerTest {
@MockBean
StatusController statusController;
@Value("${app.version}")
private String version;
@DisplayName("health check")
@Test
void healthCheckTest() throws Exception {
Map<String, String> result = new LinkedHashMap<>();
result.put("version", version);
when(statusController.healthCheck()).thenReturn(result);
mockMvc.perform(get("/status/version"))
.andDo(document("get/status-version",
getDocumentResponse(),
responseFields(
fieldWithPath("version").attributes(key("format").value("displayVersion - buildTime(yyyyMMdd-HHmmss) 형식")).description("version 명시"))
)
);
}
}
여기서는 import 문까지는 필요 없을 것 같아서 제외를 했다. 그리고 MockBean 이나 Mock, Spy 관련 내용은 좀 더 정리를 해서 글을 올리려고 한다.
참고로 필자의 경우에는 (1) 테스트 결과인 snippets 를 가지고 -> (2) adoc 문서를 원하는 모습으로 만들어준 뒤 -> (3) 이를 asciidoctor 를 돌려서 html 문서화를 해주었는데 (1) 에서 (2) 단계로 넘어가는 부분이 자동화가 될 수 있을 것 같지만 아직 이것까지는 잘 모르겠다.. 이 부분도 더 학습을 해서 해당 내용을 추가하면 좋을 것 같다.
그래서 위 순서대로 진행을 해준 뒤 index.adoc 혹은 각자가 원하는 방식으로 adoc 을 정리해서 asciidoctor 를 수행해주면 짠! 문서가 완성이 된다.
이렇게 REST Docs 설정에 대해 간략하게 정리를 해보았는데 이후에는 Mock 테스트 관련해서 더 정리를 해보면 좋을 것 같다.
참고
https://seongtak-yoon.tistory.com/27
https://jinseobbae.github.io/gradle/2022/01/11/gradle-call-task.html
'Server > Spring Boot' 카테고리의 다른 글
Spring Boot OAuth2.0 애플 로그인 & 탈퇴 구현 (4) | 2023.01.05 |
---|---|
[사이드 프로젝트 - 비사이드] Spring Boot + JPA + Querydsl 적용 (0) | 2022.12.28 |
EhCache - StringCacheKeyGenerator 사용 시 @Cacheable 적용 (0) | 2022.09.18 |
logback 설정 - 특정 변수에 따라 log 파일 다른 위치에 저장하기 (0) | 2022.08.25 |
ExceptionHandlerFilter 적용(3) (0) | 2022.07.10 |