반응형
개발 환경
- Java(11)
- Amazon Corretto JDK(11)
- Spring Boot(2.5.3)
- jvm.convert(3.3.2)
- spring-restdocs-asciidoctor
- spring-restdocs-mockmvc
Config
1. Dependency 추가 및 설정
의존성 주입해줄 라이브러리와 플러그인을 추가하고 문서를 생성할 폴더 구조를 설정
plugins {
...
// asciidoc파일을 변환해주고, Build폴더에 복사해주는 플러그인
id "org.asciidoctor.jvm.convert" version "3.3.2"
...
}
...
// ============== BEGIN Spring REST Docs ==============
ext {
snippetsDir = file('build/generated-snippets')
docsDir = file('src/docs/asciidoc')
}
asciidoctor {
configurations 'asciidoctorExtensions'
inputs.dir snippetsDir
dependsOn test
attributes 'docsDir': docsDir
}
bootJar {
dependsOn asciidoctor
copy {
from "${asciidoctor.outputDir}"
into 'src/main/resources/static/docs'
}
}
test {
outputs.dir snippetsDir
useJUnitPlatform()
}
configurations {
asciidoctorExtensions
compileOnly {
extendsFrom annotationProcessor
}
}
// ============== END Spring REST Docs ==============
...
dependencies {
...
// spring rest docs dependency 추가
asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
...
}
공통적으로 사용할 클래스, 인터페이스 생성
1. ApiDocsUtil
API문서를 생성할 때 공통적으로 적용될 Util 클래스 생성
public interface ApiDocumentUtil {
static OperationRequestPreprocessor getDocumentRequest() {
// Request Spec을 정렬해서 출력해줌
return preprocessRequest(prettyPrint());
}
static OperationResponsePreprocessor getDocumentResponse() {
// Response Spec을 정렬해서 출력해줌
return preprocessResponse(prettyPrint());
}
}
2. ApiDocsFormatGenerator
API문서를 생성할 때 Resquet, Response Spec에 대한 format을 지정해줄 인터페이스 생성
public interface ApiDocsFormatGenerator {
static Attributes.Attribute boardPostType() {
return key("format").value(BoardPostType.NOTICE + ":" + BoardPostType.NOTICE.getDescription());
}
}
3. ControllerTest
Controller Test 시 공통적으로 Mock처리를 해줄 클래스 생성
모든 TestController에서 상속받는다.
@AutoConfigureRestDocs // REST Doc 자동설정
@ExtendWith({RestDocumentationExtension.class, SpringExtension.class}) // 문서 스니펫 생성을 위한 클래스
public class ControllerTest {
/**
* Spring Security + JWT을 위한 Mock
*/
@MockBean
private JwtProvider jwtProvider;
@MockBean
private PasswordEncoder passwordEncoder;
@MockBean
private UserDetailsService userDetailsService;
@MockBean
private AuthenticationEntryPoint authenticationEntryPoint;
@MockBean
private AuthenticationSuccessHandler authenticationSuccessHandler;
@MockBean
private AuthenticationFailureHandler authenticationFailureHandler;
@MockBean
private AccessDeniedHandler accessDeniedHandler;
// 상속받은 Controller에서 사용할 MockMvc 의존성 주입
@Autowired
public MockMvc mockMvc;
// 상속받은 Controller에서 사용할 ObjectMapper 의존성 주입
@Autowired
public ObjectMapper objectMapper;
}
Test Case 진행
1. Post Test Case
...
import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post;
...
@WebMvcTest(SpringDoscExe.class) // Mocking된 컴포넌트를 사용하기 위한 환경을 설정
public class SpringDoscExeTest extends ControllerTest {
@Mockbean
private BoardService boardService;
@Test
@DisplayName("게시글 등록")
// Spring Security 인증과 권한 처리 -> 로그인 한 상태
@WithMockUser(username= "securityUsername", password="securityPassword", roles="USER")
public void postInsert() throws Exception {
// given
doNothing().when(boardService).postInsert(any(PostInsertDTO.class));
// when
PostInsertDTO request = PostInsertDTO.builder()
.type(BoardPostType.NOTICE)
.subject("게시글 제목입니다.")
.contents("게시글 내용입니다.")
.build(); // Request DTO
ResultActions result = mockMvc.perform(
post("/board/post") // API호출 url
.content(objectMapper.writeValueAsString(request)) // Request When
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
);
// then
result.andExpect(status().isOk())
.andDo(document("board/post-insert", // adoc파일을 생성할 폴더 및 파일명
getDocumentRequest(), // ApiDocumentUtils
getDocumentResponse(), // ApiDocumentUtils
requestFields( // Request Field 설정
// ApiDocsFormatGenerator에 설정한 Format을 attributes으로 적용
fieldWithPath("type").type(JsonFieldType.STRING).attributes(boardPostType()).description("게시판 유형"),
fieldWithPath("subject").type(JsonFieldType.STRING).description("게시글 제목"),
// optional() 적용 시 null 허용
fieldWithPath("contents").type(JsonFieldType.STRING).optional().description("게시글 내용")
),
responseFields( // Response Field 설정
fieldWithPath("code").type(JsonFieldType.NUMBER).description("결과코드"),
fieldWithPath("msg").type(JsonFieldType.STRING).description("결과메시지")
)
));
}
}
2. Get Test Case
...
import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
...
@WebMvcTest(SpringDoscExe.class) // Mocking된 컴포넌트를 사용하기 위한 환경을 설정
public class SpringDoscExeTest extends ControllerTest {
@Mockbean
private BoardService boardService;
@Test
@DisplayName("게시글 조회")
// Spring Security 인증과 권한 처리 -> 로그인 한 상태
@WithMockUser(username= "securityUsername", password="securityPassword", roles="USER")
public void postInquiry() throws Exception {
// given
long postId = 29;
BoardPost post = BoardPost.builder()
.id(postId)
.type(BoardPostType.NOTICE)
.subject("게시글 제목입니다.")
.contents("게시글 내용입니다.")
.build();
given(boardService.postInquiry(any(long.class))).willReturn(post);
// when
ResultActions result = mockMvc.perform(
get("/board/post/{id}", id) // API호출 url
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
);
// then
result.andExpect(status().isOk())
.andDo(document("board/post-inquiry", // adoc파일을 생성할 폴더 및 파일명
getDocumentRequest(), // ApiDocumentUtils
getDocumentResponse(), // ApiDocumentUtils
pathParameters( // Path Parameter 설정
parameterWithName("id").description("게시글 고유번호")
)
responseFields( // Response Field 설정
fieldWithPath("code").type(JsonFieldType.NUMBER).description("결과코드"),
fieldWithPath("msg").type(JsonFieldType.STRING).description("결과메시지"),
fieldWithPath("result").type(JsonFieldType.OBJECT).description("게시글"),
fieldWithPath("result.id").type(JsonFieldType.NUMBER).description("고유번호"),
// ApiDocsFormatGenerator에 설정한 Format을 attributes으로 적용
fieldWithPath("result.type").type(JsonFieldType.STRING).attributes(boardPostType()).description("유형"),
fieldWithPath("subject").type(JsonFieldType.STRING).description("제목"),
fieldWithPath("contents").type(JsonFieldType.STRING).description("내용")
)
));
}
}
adoc 파일 커스텀
Test Case 진행 후 생성되는 산출물(adoc파일)의 형식을 Custom할 수 있다.
src/test/resources/org/springframework/restdocs/templates/asciidoctor 하위에 커스텀 할 산출물의 파일명과 똑같은 snippet 파일 생성
1. request-fields.snippet
request-fields.adoc의 형식를 Custom
=== Request Fields
|===
|Path|Type|Required|Description|format
{{#fields}}
|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}}
|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}}
|{{#tableCellContent}}{{^optional}}true{{/optional}}{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
|{{#tableCellContent}}{{#format}}{{/tableCellContent}}
{{/fields}}
|===
src/test/resources/org/springframework/restdocs/templates/asciidoctor/request-fields.snippet
2. response-fields.snippet
response-fields의 형식을 Custom
=== Response Fields
|===
|Path|Type|Nullable|Description
{{#fields}}
|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}}
|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}}
|{{#tableCellContent}}{{optional}}{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
{{/fields}}
|===
src/test/resources/org/springframework/restdocs/templates/asciidoctor/response-fields.snippet2
더보기
External Libraries중 org.springframework.restdocs:spring-restdocs-code 라이브버리 하위 org/springframework/restdocs/templates/asciidoctor에서 default snippet 확인 가능
API 문서 작성
src/docs/asciidoc 하위에 adoc파일을 include해 API 문서를 작성한다.
1. include 파일 생성
API 문서에 공통적으로 들어갈 include 파일 생성
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toclevels: 2
:sectlinks:
src/docs/asciidoc/include.adoc
2. 게시판 관련 API 문서
= 게시판 관련 API
:toc:
// include.adoc을 include, docsDir은 build.gradle에서 설정
include::{docsDir}/include.adoc[]
[[board-insert]]
== 게시글 작성
=== Method & Path
include::{snippets}/board/post-insert/http-request.adoc[]
include::{snippets}/board/post-insert/request-fields.adoc[]
include::{snippets}/board/post-insert/response-fields.adoc[]
=== Example
include::{snippets}/board/post-insert/curl-request.adoc[]
include::{snippets}/board/post-insert/response-body.adoc[]
[[board-insert]]
== 게시글 조회
=== Method & Path
include::{snippets}/board/post-inquiry/path-parameters.adoc[]
include::{snippets}/board/post-inquiry/http-request.adoc[]
include::{snippets}/board/post-inquiry/response-fields.adoc[]
=== Example
include::{snippets}/board/post-inquiry/curl-request.adoc[]
include::{snippets}/board/post-inquiry/response-body.adoc[]
빌드
- 터미널에서 ./gradlew clean build test
- BUILD SUCCESSFUL시 src/main/resources/static/docs 하위에 생성된 API문서(.html) 확인
- localhost:8080/docs/(생성한 API 문서 명).html 접속 후 확인
반응형
'Spring' 카테고리의 다른 글
Querydsl 설명 및 예제 (0) | 2021.09.15 |
---|---|
Spring AOP란? (0) | 2021.09.11 |
Spring REST Docs란? (0) | 2021.09.10 |
Spring Security와 JWT 사용 예제 (0) | 2021.09.10 |
Spring Security란? (0) | 2021.09.10 |