์• ์ •์ฝ”๋”ฉ ๐Ÿ’ป
article thumbnail

์„œ๋น„์Šค ๊ตฌํ˜„์— ์•ž์„œ REST API ๋ฌธ์„œ๋ฅผ ์ž๋™ํ™” ํ•ด์ฃผ๋Š” Spring REST Docs ๋ฅผ ์‚ฌ์šฉํ•ด๋ณธ๋‹ค.

Docs๋ฅผ ์‚ฌ์šฉํ•œ ์ด์œ ๋Š” Test์—์„œ ์„ฑ๊ณตํ•ด์•ผ๋งŒ ๋ฌธ์„œ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉํ•˜๊ฒŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๊ฐ•์ œ๋กœ TEST CODE๋ฅผ ์ž‘์„ฑํ•˜์—ฌ API์— ๋Œ€ํ•œ ์‹ ๋ขฐ์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

 

Gradle 7์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ๋Š”๋ฐ ๋ฌธ์„œ์ƒ์„ฑ์ด ์•ˆ๋˜์„œ ๊ฒ€์ƒ‰ํ•ด๋ดค๋‹ค.

์šฐํšŒ๋ฐฉ๋ฒ•์ด ์žˆ์ง€๋งŒ ๋ณต์žกํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค์šด๊ทธ๋ ˆ์ด๋“œ๋ฅผ ์„ ํƒํ–ˆ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  Gradle๊ณผ Maven ์—์„œ ๋‹ค๋ฅด๊ฒŒ ์„ค์ •๋˜๋Š” ๋ถ€๋ถ„์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋งจ์•„๋ž˜ ๋งํฌ๋ฅผ ํ†ตํ•ด ํ™•์ธํ•ด๋ณด๋Š” ๊ฒƒ๋„ ์ข‹๋‹ค!

 

ํ™˜๊ฒฝ์„ค์ •
Springboot 2.6.1
Gradle 6.9.1
JUnit5
Asciidoctor 1.5.9.2

build.gradle ์— ์ถ”๊ฐ€ํ•œ ์„ค์ •

plugins {
   
    id "org.asciidoctor.convert" version "1.5.9.2"
}

dependencies {
    asciidoctor 'org.springframework.restdocs:spring-restdocs-asciidoctor'
    testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
}

ext {
    snippetsDir = file('build/generated-snippets')
}

test {
    useJUnitPlatform()
    outputs.dir snippetsDir
}

asciidoctor {
    inputs.dir snippetsDir
    dependsOn test
}

bootJar {
    dependsOn asciidoctor
    from ("${asciidoctor.outputDir}/html5") {
        into 'static/docs'
    }
}

task copyDocument(type: Copy) {
    dependsOn asciidoctor

    from file("build/asciidoc/html5/")
    into file("src/main/resources/static/docs")
}

build {
    dependsOn copyDocument
}

๋ฌธ์„œ๋Š” Buildํ•  ๋•Œ build/asciidoc/html5/ ์— ์ €์žฅ๋˜๋Š”๋ฐ src/main/resources/static/docs ์—๋„ ๋ณต์‚ฌ๊ฐ€ ๋˜์–ด ๋ฐฐํฌ์‹œ

๋”ฐ๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

TEST CODE ์„ค์ •

@ExtendWith(RestDocumentationExtension.class)
@SpringBootTest
class UserRegisterControllerTest {

  private MockMvc mockMvc;

  @BeforeEach
  public void setUp(
      WebApplicationContext webApplicationContext,
      RestDocumentationContextProvider restDocumentation) {
    this.mockMvc =
        MockMvcBuilders.webAppContextSetup(webApplicationContext)
            .apply(documentationConfiguration(restDocumentation))
            .build();
  }

}

์ผ๋ฐ˜์ ์ธ MockMvc ์„ธํŒ… ์— ์ถ”๊ฐ€๋กœ apply(documentationConfiguration(restDocumentation)) ์„ ์ถ”๊ฐ€ ํ•ด์ฃผ์–ด ๋ฌธ์„œํ™”๋ฅผ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

์œ ์ € ์ƒ์„ฑ ํ…Œ์ŠคํŠธ

@Test
  void create() throws Exception {
    final UserRegisterResponse userRegisterResponse = UserRegisterResponse.builder().id(1L).build();
    when(userRegisterService.addUser(any())).thenReturn(userRegisterResponse);

    this.mockMvc
        .perform(
            post("/user")
                .content("{\"socialToken\":\"abc\"}")
                .contentType(MediaType.APPLICATION_JSON))
        .andExpect(status().isCreated())
        .andDo(document("user-create",requestFields(
                fieldWithPath("socialToken").description("์†Œ์…œํ† ํฐ")
        )));
  }

post ๋ฉ”์„œ๋“œ ์ด๊ธฐ ๋•Œ๋ฌธ์— content์— ํ•„์š”ํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ json ๋ฐฉ์‹์œผ๋กœ ์ž…๋ ฅํ•ด์ค๋‹ˆ๋‹ค.

 

1. user-create ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด buildํŒŒ์ผ ๋‚ด์— ์ƒ๊ธฐ๋Š” ์ด๋ฆ„์„ ์„ค์ •ํ•ด์ค๋‹ˆ๋‹ค.

2. fieldWithPath().description() ์€ ์•„๋ž˜์™€ ๊ฐ™์ด ์ถœ๋ ฅํ•ด์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋งˆ์ง€๋ง‰์œผ๋กœ src/docs/asclidoc/api-docs.adoc ๋ฅผ ๋งŒ๋“ค์–ด์„œ ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

* api-docs ๋Š” http://localhost:8080/docs/api-docs.html ์™€ ๊ฐ™์ด

html ํŒŒ์ผ์˜ ์ด๋ฆ„์„ ์ •ํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ๊ด€๋ จ๋œ ์ด๋ฆ„์œผ๋กœ ์ง€์–ด์ค๋‹ˆ๋‹ค.

 

๊ณต์‹๋ฌธ์„œ ์ฐธ๊ณ  ( ๋ฒˆ์—ญ๋ณธ๋„ ์žˆ๋Š”๋“ฏ... )

https://docs.asciidoctor.org/asciidoctor/latest/

 

Asciidoctor - Asciidoctor Documentation

A documentation page for Asciidoctor.

docs.asciidoctor.org

= Spring REST Docs
:toc: left
:toclevels: 2
:sectlinks:

[[resources-post]]
== User

[[resources-post-create]]
=== User ์ƒ์„ฑ

==== Request Fields

include::{snippets}/user-create/request-fields.adoc[]

==== HTTP request

include::{snippets}/user-create/http-request.adoc[]

==== HTTP response

include::{snippets}/user-create/http-response.adoc[]

include ์— ํ•ด๋‹นํ•˜๋Š” adoc๋Š” build/generated-snippets ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

 

 

์„œ๋ฒ„ ์‹คํ–‰ํ›„ 

http://localhost:8080/docs/api-docs.html ๋กœ ์ ‘์†ํ•˜๋ฉด ๋ฌธ์„œ๊ฐ€ ๋งŒ๋“ค์–ด์ง„๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

 

+ 2022.02.09 List ์กฐํšŒ

List๋กœ ๋œ Response์ผ ๊ฒฝ์šฐ fieldWithPath๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด []. ๋ฅผ ๋ถ™์—ฌ์ค€๋‹ค 

์ด์œ ๋Š” -> https://docs.spring.io/spring-restdocs/docs/2.0.4.RELEASE/reference/html5/#documenting-your-api-request-response-payloads-fields-reusing-field-descriptors

@Test
  void findAllWish() throws Exception {
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy๋…„ MM์›” dd์ผ E์š”์ผ", Locale.KOREAN);

    final List<WishListFindAllResponse> wishListFindAllResponse =
        Lists.newArrayList(
            WishListFindAllResponse.builder()
                .wishListId(1L)
                .complete_date(null)
                .register_date(
                    simpleDateFormat.format(java.sql.Timestamp.valueOf(LocalDateTime.now())))
                .content("๋ฐ”๋‹ค ๊ฐ€๊ธฐ")
                .wishUserNickname("์• ์ •")
                .status(WishStatus.INCOMPLETE)
                .build());

    when(wishFindService.findAllWish(any())).thenReturn(wishListFindAllResponse);

    this.mockMvc
        .perform(get("/wishlists/{coupleToken}", "AAAAA").accept(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk())
        .andDo(
            document(
                "wish-find-all",
                pathParameters(parameterWithName("coupleToken").description("์ปคํ”Œ ํ† ํฐ")),
                responseFields(
                    fieldWithPath("[].wishListId").description("์œ„์‹œ ๊ณ ์œ  ID"),
                    fieldWithPath("[].content").description("์œ„์‹œ ๋‚ด์šฉ"),
                    fieldWithPath("[].register_date").description("์œ„์‹œ ์ƒ์„ฑ ๋‚ ์งœ"),
                    fieldWithPath("[].complete_date").description("์œ„์‹œ ์™„๋ฃŒ ๋‚ ์งœ"),
                    fieldWithPath("[].wishUserNickname").description("์œ„์‹œ ์ƒ์„ฑํ•œ ์œ ์ €์˜ ๋‹‰๋„ค์ž„"),
                    fieldWithPath("[].status").description("์œ„์‹œ ์ƒํƒœ"))));
  }

 

 

 

 

์ฐธ๊ณ  

https://tecoble.techcourse.co.kr/post/2020-08-18-spring-rest-docs/

 

API ๋ฌธ์„œ ์ž๋™ํ™” - Spring REST Docs ํŒ”์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค

ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ์™€ ๋ถ„๋ฆฌํ•˜์—ฌ ๋ฌธ์„œ ์ž๋™ํ™”๋ฅผ ํ•˜๊ณ  ์‹ถ๋‹ค๊ณ ์š”? ์‹ ๋ขฐ๋„ ๋†’์€ API ๋ฌธ์„œ๋ฅผ ๋งŒ๋“ค๊ณ  ์‹ถ๋‹ค๊ณ ์š”? ํ…Œ์ŠคํŠธ๊ฐ€ ์„ฑ๊ณตํ•ด์•ผ ๋ฌธ์„œ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค!! Spring REST Docs๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. API ๋ฌธ์„œ๋ฅผ ์ž๋™ํ™” ๋„๊ตฌ๋กœ

tecoble.techcourse.co.kr

https://blog.hodory.dev/2019/12/04/spring-rest-docs-with-gradle-not-working-html5/

 

๏ผปJava๏ผฝSpring REST Docs HTML์ด ์ƒ์„ฑ๋˜์ง€ ์•Š์„๋•Œ

๋ฐฑ๊ธฐ์„ ๋‹˜์˜ ์Šคํ”„๋ง๋ถ€ํŠธ ๊ฐ•์ขŒ๋ฅผ ์ˆ˜๊ฐ•ํ•˜๋Š”์ค‘์— Spring REST Docs๋ฅผ ์ด์šฉํ•˜์—ฌ HTML์„ ์ƒ์„ฑํ•˜๋ คํ•˜๋Š”๋ฐ,์•„๋ฌด๋ฆฌ ๋นŒ๋“œ๋ฅผ ํ•ด๋„ ascii\html\index.html์ด ์ƒ์„ฑ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. 12345678910111213141516171819202122232425262728293

blog.hodory.dev

 

๋ฐ˜์‘ํ˜•

'Project > DARLING' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

7. CI/CD ๊ณ„ํš  (0) 2022.02.09
6. ๋ฌด์—‡์ด ๋ฌธ์ œ์ผ๊นŒ ...  (0) 2022.01.25
4. ์—ฐ๊ด€๊ด€๊ณ„ - OneToMany  (0) 2022.01.23
3. ์—ฐ๊ด€๊ด€๊ณ„ - ManyToOne  (0) 2022.01.23
2. Entity  (0) 2021.12.03