RestDocs 도입 - nhnacademy-be10-WannaB/wannab-wiki GitHub Wiki

1. 의존성 추가 및 빌드 관리

		<dependency>
			<groupId>org.springframework.restdocs</groupId>
			<artifactId>spring-restdocs-mockmvc</artifactId>
			<version>3.0.0</version>
			<scope>test</scope>
		</dependency>
  • 이거 추가
  • test할때만 쓰니까 scope는 test
			<plugin>
				<groupId>org.asciidoctor</groupId>
				<artifactId>asciidoctor-maven-plugin</artifactId>
				<version>2.2.1</version>
				<executions>
					<execution>
						<id>generate-docs</id>
						<phase>prepare-package</phase>
						<goals>
							<goal>process-asciidoc</goal>
						</goals>
						<configuration>
							<backend>html</backend>
							<doctype>book</doctype>
						</configuration>
					</execution>
				</executions>
				<dependencies>
					<dependency>
						<groupId>org.springframework.restdocs</groupId>
						<artifactId>spring-restdocs-asciidoctor</artifactId>
						<version>3.0.0</version>
					</dependency>
				</dependencies>
			</plugin>
  • plugin에는 이렇게 추가
    • 마지막꺼 빨간색으로 나올수도 있음
image
  • 저도 이렇게 나옴;;
    • 왜이러는지 모르겠음
    • mvn repository에도 있고, spring rest docs 공식 문서에도 이렇게 작성하는 것을 권장하고 있는데 이럼..
      • 인텔리제이 오류인가?
      • 막상 코드 테스트 해보면 잘되긴함

2. 인텔리제이 플러그인 설치

image
  • 없어도 되긴하는데 있으면 편함

3. 테스트 코드 작성

테스트 코드 클래스 위에 작성할 것들

  • Rest Docs는 API 문서이기 때문에, 컨트롤러 테스트 코드만 Rest Docs 관련 테스트 코드가 들어가면 됨.
  • Service, Repository 단은 하던대로 테스트 ㄱㄱ
@ActiveProfiles("ci")
@AutoConfigureRestDocs
@DisplayName("Category Controller 단위 테스트")
@WebMvcTest(CategoryController.class)
class CategoryControllerTest {
  • 테스트 코드 작성시 프로필을 설정해주지 않으면 application.yml 이 없어서 에러가 남.
    • ci 로 명시해주세요.
    • dev로 하면 로컬에서는 잘되겠지만 github에 올려서 자동 테스트 돌리면 실패합니다
      • 왜? github에는 application-dev.yml 이 없으니까
  • @AutoConfigureRestDocs
    • Restdocs 관련 어노테이션 붙여주세요

실제 단위 테스트 시 사용할 코드들

    @Test
    @DisplayName("상위 카테고리 생성 테스트 : 성공")
    void createParentCategory() throws Exception {
        CategoryCreateRequest request = new CategoryCreateRequest("소설");

        mockMvc.perform(post("/api/categories")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(mapper.writeValueAsString(request)))
                .andExpect(status().isCreated())
                .andDo(document("categories/create-parent",
                        requestFields(
                                fieldWithPath("name").description("카테고리 이름")
                        )
                ));

        verify(categoryService).createParentCategory(request);
    }
  • 주의할점은 post 부분에서 원래는 mockmvc 관련 import를 사용해야하는데 이제는 restdocs import 를 해주어야함

    image.png

    • 요렇게
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post;
  • import 찾기 힘들면 이거 복붙해서 쓰세요 ㅎ
  • 나머지 테스트 코드 부분은 다 똑같은데 다음으로 중요한건 andDo 절임
                .andDo(document("categories/create-parent",
                        requestFields(
                                fieldWithPath("name").description("카테고리 이름")
                        )
                ));
  • 이걸 해주면 이게 문서로 만들어짐

4. 테스트 성공 후 target/generated-snippets 폴더 확인하기

image

이렇게 문서가 만들어짐. 이런 문서를 스니펫이라고 함. 스니펫이라는게 조각이라는 뜻이니까 문서 조각들이구나~ 하고 이해하면 됨.

restdocs 썻다고하면 면접관이 물어봄 “스니펫 구성 어케했냐?” 이런식으로 그때 얼타면 안되고 ‘아 스니펫은 이거였지?’ 하고 이해하면 됨.

5. 만들어진 snippet을 조합해서 예쁘게 문서 만들어보기

먼저, src/docs/asciidoc/ 이라는 디렉토리 구조를 만들고

  • 만약 이 폴더가 없으면 (사실 당연히 없어야함) 만들면 됨.

해당 디렉토리에 index.adoc 파일을 만들어주기

아래와 같이 만들어주면 됨.

= Wannab Book API 문서
:toc: left
:toclevels: 2
:sectnums:
:sectnumlevels: 3

== 카테고리 생성

include::{snippets}/categories/create-parent/http-request.adoc[]
include::{snippets}/categories/create-parent/http-response.adoc[]
include::{snippets}/categories/create-parent/request-fields.adoc[]
  • adoc 문법인데, = 이거 하나는 마크다운에 # 하나하면 제목이 되는거랑 똑같음
    • 밑에 toc, toclevels 이런건 저도 잘 몰라요 근데 목차만들때쓰는거임
  • include 문법은 adoc 문법인데 그냥 다른 adoc을 가지고 와서 여기에 포함시키겠다는 말임 불러오기처럼
  • 뒤에 {snippets} 경로는 만들어진 target 밑의 스니펫 경로를 가지고 오는거임

다만들고 나면..

image
  • 이렇게 이쁘게 됨
  • 우리모두 이쁜 Rest docs를 써봐요~~
    • 색도 넣을 수 있고 예쁘게 꾸밀수도있어요. (개귀찮긴하지만)

하다보니 궁금한 점..(아는 사람 답변좀..)

    @Test
    @DisplayName("카테고리 조회 테스트 : 특정 부모 ID의 하위 카테고리 페이징 조회")
    void findCategories_child_with_paging() throws Exception {
        Long parentId = 1L;
        Page<CategoryResponse> mockResponse = new PageImpl<>(
                List.of(
                        new CategoryResponse(3L, "고전"),
                        new CategoryResponse(4L, "현대소설")
                ),
                PageRequest.of(0, 10),
                2
        );

        given(categoryService.findChildCategoriesByParentId(eq(parentId), any())).willReturn(mockResponse);

        mockMvc.perform(get("/api/categories")
                        .param("parentId", parentId.toString())
                        .param("page", "0")
                        .param("size", "10"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.content.length()").value(2))
                .andDo(print())
                .andDo(document("categories/find-child",
                        queryParameters(
                                parameterWithName("parentId").description("부모 카테고리 ID (필수)"),
                                parameterWithName("page").optional().description("페이지 번호 (기본값: 0)"),
                                parameterWithName("size").optional().description("페이지 크기 (기본값: 10)")
                        ),
                        responseFields(
                                fieldWithPath("content[].id").description("카테고리 ID"),
                                fieldWithPath("content[].name").description("카테고리 이름"),
                                subsectionWithPath("pageable").description("페이지 정보"),
                                fieldWithPath("totalPages").description("전체 페이지 수"),
                                fieldWithPath("totalElements").description("전체 요소 수"),
                                fieldWithPath("last").description("마지막 페이지 여부"),
                                fieldWithPath("size").description("페이지 크기"),
                                fieldWithPath("number").description("현재 페이지 번호"),
                                fieldWithPath("sort").description("정렬 정보"),
                                fieldWithPath("numberOfElements").description("현재 페이지 요소 수"),
                                fieldWithPath("first").description("첫 페이지 여부"),
                                fieldWithPath("empty").description("비어있는 페이지 여부"),
                                fieldWithPath("sort.empty").description("정렬 조건 없음"),
                                fieldWithPath("sort.sorted").description("정렬 적용 여부"),
                                fieldWithPath("sort.unsorted").description("정렬 비적용 여부")
                        )
                ));

        verify(categoryService).findChildCategoriesByParentId(eq(parentId), any());
    }
  • 모든 요소에 대해서 문서화를 해야해서 페이징을 하는 경우 저렇게 엄청난 코드들이 들어가게 되는데 이게 맞나 싶음..
  • 아는사람 @훈민 김 에게 알려주시면 땡큐땡큐요

레퍼런스

[Spring REST Docs](https://docs.spring.io/spring-restdocs/docs/current/reference/htmlsingle/)

⚠️ **GitHub.com Fallback** ⚠️