본문 바로가기
스프링

[스프링] REST Docs, asciidoctor 로 API 문서 관리하기

by kdohyeon (김대니) 2023. 2. 6.
반응형

테스트 대상 Controller

@RestController
public class BlogSearchController {

    private final SearchBlogUseCase searchBlogUseCase;

    public BlogSearchController(SearchBlogUseCase searchBlogUseCase) {
        this.searchBlogUseCase = searchBlogUseCase;
    }

    @GetMapping("/api/v1/blogs")
    public ResultResponse<BlogDto> searchBlogs(
            @Valid SearchBlogRequestBody requestBody,
            @RequestParam(required = false, defaultValue = "1") int page,
            @RequestParam(required = false, defaultValue = "10") int size
    ) {
        var command = BlogSearchCommand.builder()
                .keyword(requestBody.getKeyword())
                .url(requestBody.getUrl())
                .sort(requestBody.getSort())
                .page(page)
                .size(size)
                .build();

        return ResultResponse.ok(searchBlogUseCase.search(command));
    }
}

의존성 추가하기

asciidoctor 는 restdocs 로 만들어지는 adoc 파일을 HTML 등과 같은 파일로 파싱해주는 역할을 한다.

plugins {
    id("com.epages.restdocs-api-spec") version Versions.restdocsApiSpec apply false
    id("org.asciidoctor.jvm.convert") version Versions.asciidoctorPlugin apply false
}

dependencies {
    integrationImplementation("org.springframework.boot:spring-boot-starter-test")
    integrationImplementation("org.springframework.restdocs:spring-restdocs-mockmvc")
    integrationImplementation("io.rest-assured:spring-mock-mvc")
}

설정 파일 만들기

@SpringBootTest
@ExtendWith({RestDocumentationExtension.class})
public abstract class BlogApiTest {

    @Autowired
    WebApplicationContext webApplicationContext;

    @BeforeEach
    void setup(RestDocumentationContextProvider restDocumentation) {
        RestAssuredMockMvc.webAppContextSetup(
                webApplicationContext,
                documentationConfiguration(restDocumentation)
        );
    }

    protected OperationRequestPreprocessor defaultPreprocessRequest() {
        return preprocessRequest(
                prettyPrint()
        );
    }

    protected OperationResponsePreprocessor defaultPreprocessResponse() {
        return preprocessResponse(
                prettyPrint()
        );
    }
}

API 테스트 작성하기

@ExtendWith(MockitoExtension.class)
public class BlogSearchControllerTest extends BlogApiTest {
    @Mock
    private SearchBlogUseCase searchBlogUseCase;

    @BeforeEach
    void setup(RestDocumentationContextProvider restDocumentation) {
        var blogSearchController = new BlogSearchController(searchBlogUseCase);

        standaloneSetup(
                MockMvcBuilders
                        .standaloneSetup(blogSearchController)
                        .apply(documentationConfiguration(restDocumentation))
        );
    }

    @Test
    void searchBlogs() {
        when(searchBlogUseCase.search(any()))
                .thenReturn(getMockBlogDto());

        var keyword = "키워드";

        var response = given()
                .accept(ContentType.JSON)
                .param("keyword", keyword)
                .param("page", 1)
                .param("size", 10)
                .when()
                .get("/api/v1/blogs");
        response.prettyPrint();

        response.then()
                .status(HttpStatus.OK)
                .apply(
                        document(
                                "search-blogs",
                                resourceDetails().tag("블로그").description("블로그 조회 (페이징)"),
                                defaultPreprocessRequest(),
                                defaultPreprocessResponse(),
                                requestParameters(
                                        parameterWithName("keyword").description("블로그 검색 키워드"),
                                        parameterWithName("url").description("블로그 검색 주소").optional(),
                                        parameterWithName("sort").description("정렬 기준 [정확도순: ACCURACY, 최신순: RECENCY]").optional(),
                                        parameterWithName("page").description("전시 유무").optional(),
                                        parameterWithName("size").description("페이지 번호").optional()
                                ),
                                responseFields(
                                        fieldWithPath("success")
                                                .type(JsonFieldType.BOOLEAN)
                                                .description("성공 여부"),
                                        fieldWithPath("code")
                                                .type(JsonFieldType.STRING)
                                                .description("code"),
                                        fieldWithPath("message")
                                                .type(JsonFieldType.STRING)
                                                .description("message")
                                                .optional(),
                                        fieldWithPath("data")
                                                .type(JsonFieldType.OBJECT)
                                                .description("데이터"),
                                        fieldWithPath("data.documents[]")
                                                .type(JsonFieldType.ARRAY)
                                                .description("블로그 문서 목록"),
                                        fieldWithPath("data.documents[].title")
                                                .type(JsonFieldType.STRING)
                                                .description("블로그 제목"),
                                        fieldWithPath("data.documents[].contents")
                                                .type(JsonFieldType.STRING)
                                                .description("블로그 내용"),
                                        fieldWithPath("data.documents[].url")
                                                .type(JsonFieldType.STRING)
                                                .description("블로그 주소"),
                                        fieldWithPath("data.documents[].blogName")
                                                .type(JsonFieldType.STRING)
                                                .description("블로그 이름"),
                                        fieldWithPath("data.documents[].thumbnail")
                                                .type(JsonFieldType.STRING)
                                                .description("이미지 썸네일"),
                                        fieldWithPath("data.documents[].writtenAt")
                                                .type(JsonFieldType.STRING)
                                                .description("블로그 글 작성 시간"),
                                        subsectionWithPath("data.pagination")
                                                .type(JsonFieldType.OBJECT)
                                                .description("페이지네이션")
                                )
                        )
                );
    }

    private BlogDto getMockBlogDto() {
        var fixtureMonkey = FixtureMonkey.builder()
                .putGenerator(BlogDocumentDto.class, BuilderArbitraryGenerator.INSTANCE)
                .build();

        var sampleBlogs = fixtureMonkey.giveMeBuilder(BlogDocumentDto.class)
                .setNotNull("title")
                .setNotNull("contents")
                .setNotNull("url")
                .setNotNull("blogName")
                .setNotNull("thumbnail")
                .setNotNull("writtenAt")
                .sampleList(10);

        return BlogDto.builder()
                .documents(sampleBlogs)
                .pagination(Pagination.empty())
                .build();
    }
}

 

참고자료

FixtureMonkey 관련

https://mangchhe.github.io/test/2022/07/12/FixtureMonkey/

 

[Spring Boot] Fixture Monkey - 테스트 객체 쉽게 만들기

Fixture Monkey를 이용해서 좀 더 가독성 있는 테스트 객체를 만들어보자.

mangchhe.github.io

 

반응형

댓글