fee-fi-fo-fum
article thumbnail
๋ฐ˜์‘ํ˜•

1. ๋“ค์–ด๊ฐ€๊ธฐ ์ „

์ด๋ฒˆ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ API๋ฌธ์„œ๋ฅผ REST Docs๋ฅผ ์‚ฌ์šฉํ•ด ๋งŒ๋“ค๊ธฐ๋กœ ํ•˜์˜€๋‹ค. API ๋ช…์„ธ์„œ๋ฅผ REST Docs๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ž‘์„ฑํ•˜๋Š”๊ฑด ์ฒ˜์Œ์ด์—ฌ์„œ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•˜๊ธฐ์ „์— ์ด๋Ÿฐ์ €๋Ÿฐ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•ด ๋ณด์•˜๋‹ค. ๊ทธ๋Ÿผ ์ง€๊ธˆ๋ถ€ํ„ฐ ์•Œ์•„๋ณด์ž!

2. REST Docs? 

Spring REST Docs๋Š” ์ •ํ™•ํ•˜๊ณ  ๊ฐ€๋…์„ฑ ์ข‹์€ REST ๋ฌธ์„œ๋ฅผ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•œ ํ”„๋กœ์ ํŠธ์ด๋‹ค. Asciidoctor๋ฅผ ํ™œ์šฉํ•ด์„œ ํ…Œ์ŠคํŠธ์ฝ”๋“œ๋ฅผ adoc์œผ๋กœ ๋ณ€ํ™˜ํ›„ HTML๋กœ ๋ณ€ํ™˜์‹œ์ผœ์ฃผ๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•œ๋‹ค. ๋Œ€์•ˆ์œผ๋กœ Markdown์„ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

 

Adoc?
adoc์€ ๋ฌธ์„œ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•œ ๊ฒฝ๋Ÿ‰ํ˜• ๋งˆํฌ์—… ์–ธ์–ด์ด๋‹ค. asciidoctor๋ฅผ ํ†ตํ•ด html์ด๋‚˜ pdf๋“ฑ์˜ ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ํ™œ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

 

๋จผ์ € Spring MVC์˜ test์—์„œ ์ œ๊ณตํ•˜๋Š” MockMvc๋กœ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ์‚ฌ์šฉํ•œ๋‹ค.์—ฌ๊ธฐ์„œ REST Docs์˜ ์žฅ์ ์ด ๋‚˜์˜จ๋‹ค. ๋ฐ”๋กœ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด REST API ๋ฌธ์„œ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฒ€์ฆ๋˜์—ˆ๋‹ค๋ฉด ์ž‘์„ฑ๋˜๋Š” ๋ฌธ์„œ๋„ ์‹ ๋ขฐํ• ์ˆ˜์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

 

2.1. REST Docs vs Swagger

์Šค์›จ๊ฑฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ์— ๋ฌธ์„œํ™”์— ๋Œ€ํ•œ ์ฝ”๋“œ๊ฐ€ ํฌํ•จ๋˜์–ด, ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์ง€๊ฒŒ๋œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  API ์ŠคํŽ™์ด ๋ณ€๊ฒฝ๋˜์—ˆ์„๋•Œ, ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์œผ๋ฉด API ๋ฌธ์„œ๊ฐ€ ์ˆ˜์ •๋˜์ง€ ์•Š๋Š”๋‹ค.

 

์ด์—๋ฐ˜ํ•ด REST Docs๋Š” ํ…Œ์ŠคํŠธ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด์„œ API๋ฅผ ๋ช…์„ธ ํ• ์ˆ˜์žˆ๋‹ค. ๋˜ํ•œ ํ…Œ์ŠคํŠธ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ์ž‘์„ฑ๋œ ์Šค๋‹ˆํŽซ๊ณผ ์ง์ ‘์ž‘์„ฑํ•œ ๋ฌธ์„œ๋ฅผ ๊ฒฐํ•ฉํ•˜์—ฌ ์ตœ์ข…์ ์ธ API ๋ฌธ์„œ๋ฅผ ๋งŒ๋“ค์ˆ˜์žˆ์œผ๋ฏ€๋กœ ์ œํ’ˆ ์ฝ”๋“œ์— ์˜ํ–ฅ์ด ์—†๋‹ค. ํ•˜์ง€๋งŒ ํ…Œ์ŠคํŠธ ๊ธฐ๋ฐ˜์œผ๋กœ ์ž‘์„ฑ๋˜๋‹ค๋ณด๋‹ˆ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•˜๋ฉด ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ• ์ˆ˜๊ฐ€์—†๊ณ , ๋ฌธ์„œํ™”๋ฅผ ์œ„ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•ด์•ผํ•œ๋‹ค๋Š” ์ ์ด๋‹ค. TDD๋ฐฉ์‹์„ ์ ์šฉํ•œ๋‹ค๋ฉด ์˜คํžˆ๋ ค ์ข‹์€๊ฒŒ ์•„๋‹๊นŒ?

 

2.2. MockMvc vs RestAssured

RestDocs์—์„œ ์‚ฌ์šฉํ• ์ˆ˜์žˆ๋Š” 2๊ฐ€์ง€ ๋ฐฉ์‹์ด๋‹ค. RestAssured๋Š” ๋ณ„๋„์˜ ๊ตฌ์„ฑ์„ ํ•˜์ง€์•Š๋Š”๋‹ค๋ฉด, @SpringBootTest ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ๋˜์–ด์•ผํ•˜๋Š”๋ฐ, @SpringBootTest๋Š” ์Šคํ”„๋ง์˜ ์ „์ฒด ๋นˆ์„ ์ปจํ…์ŠคํŠธ์— ๋„์–ด์„œ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์„ ๊ตฌ๋™ํ•œ๋‹ค. ์ฆ‰, ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋™์ž‘ํ•˜๋Š” ์‹ค์ œํ™˜๊ฒฝ๊ณผ ๋™์ผํ•˜๊ฒŒ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๊ณ  ์‹ถ์„๋•Œ ์‚ฌ์šฉํ•œ๋‹ค. ์ด์— ๋”ฐ๋ผ์„œ @SpringBootTest๋ฅผ ํ†ตํ•ด ๋ชจ๋“  ๋นˆ์ด ๋“ฑ๋ก ๋˜๋Š” ๊ณผ์ •์—์„œ ๋Š๋ ค์ง€๊ณ  ๋น„์šฉ์ด ๋งŽ์ด๋“ ๋‹ค. 

 

์ด์—๋ฐ˜ํ•ด MockMvc ๋Š” @SpringBootTest , @WebMvcTest ์ค‘ ์„ ํƒํ•ด์„œ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค. @WebMvcTest๋Š” ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ๋ ˆ์ด์–ด์˜ ๋นˆ๋“ค๋งŒ ๋กœ๋“œํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋‚˜๋จธ์ง€ ๊ณ„์ธต์€ Mocking์„ ํ•œ๋‹ค. ์ด๋Ÿฐ ๋ฐฉ์‹์œผ๋กœ ํ•˜๋‚˜์˜ ๊ณ„์ธต๋งŒ์„ ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒƒ์„ ์Šฌ๋ผ์ด์Šค ํ…Œ์ŠคํŠธ๋ผ๊ณ  ํ•˜๋ฉฐ, ์ผ๋ฐ˜์ ์œผ๋กœ ๋ฌธ์„œํ™”๋ฅผ ์œ„ํ•ด ์ปจํŠธ๋กค๋Ÿฌ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ• ๋•Œ๋Š” ์ปจํŠธ๋กค๋Ÿฌ ์ด์™ธ์˜ ๊ณ„์ธต์„ Mockingํ•˜์—ฌ ์ง„ํ–‰ํ•œ๋‹ค. 

 

 

3. REST Docs ์‚ฌ์šฉ

๋‚ด๊ฐ€ ๊ตฌ์„ฑํ•œ ํ™˜๊ฒฝ์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  • Java 11
  • Gradle 8.8
  • Spring 2.7

3.1. Gradle ์„ค์ •

[ํ”Œ๋Ÿฌ๊ทธ์ธ ์ถ”๊ฐ€]

<bash />
plugins { // ... id 'org.asciidoctor.jvm.convert' version '3.3.2' }

Asciidoctor ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ ์šฉํ•œ๋‹ค.
- Gradle 7 ๋ฒ„์ „ ์ด์ƒ์ผ ๋•Œ org.asciidoctor.jvm.convert 3.3.2 ๋ฒ„์ „์„ ์‚ฌ์šฉํ•œ๋‹ค.
- Gradle 7 ๋ฒ„์ „ ๋ฏธ๋งŒ์ผ ๋•Œ org.asciidoctor.convert 2.4.0 ๋ฒ„์ „์„ ์‚ฌ์šฉํ•œ๋‹ค.

 

[๊ตฌ์„ฑ ์ถ”๊ฐ€]

<bash />
configurations { // ... asciidoctorExt }

Asciidoctor๋ฅผ ํ™•์žฅํ•˜๋Š” ์ข…์†์„ฑ์— ๋Œ€ํ•œ ๊ตฌ์„ฑ์„ ์„ ์–ธํ•œ๋‹ค.

 

[์˜์กด์„ฑ ์ถ”๊ฐ€]

<bash />
dependencies { // ... asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor' testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' }

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—์„œ MockMvc๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•˜๋‹ค.

 

[๋ณ€์ˆ˜ ์„ ์–ธ]

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

 

[์ž‘์—… ์„ค์ •]

<bash />
test { useJUnitPlatform() outputs.dir snippetsDir } asciidoctor { inputs.dir snippetsDir configurations 'asciidoctorExt' dependsOn test }

- test ์ž‘์—… ์‹œ output์„ ์œ„์—์„œ ์„ค์ •ํ•œ snippetsDir์— ๋‹ด๋„๋ก ํ•œ๋‹ค.
- asciidoctor ์ž‘์—…์€ test ์ž‘์—…์„ ์˜์กดํ•˜๊ธฐ์—, test ์ž‘์—…์„ ๋จผ์ € ์ˆ˜ํ–‰ํ•˜๋„๋ก ํ•œ๋‹ค.
- asciidoctor ์ž‘์—…์— ํ•„์š”ํ•œ input์ด snippetsDir์— ์žˆ๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
- ๊ตฌ์„ฑ ์ถ”๊ฐ€์—์„œ ํ™•์žฅํ•œ asciidoctorExt๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

 

[Jar ๋นŒ๋“œ ์ž‘์—…]

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

ํ”„๋กœ์ ํŠธ๋ฅผ Jar ํŒŒ์ผ๋กœ ์ƒ์„ฑ์‹œ์— Rest Docs๊ฐ€ ํ•ด๋‹น ๊ฒฝ๋กœ์— ์œ„์น˜ํ• ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.

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

์ด๋•Œ ํ˜น์‹œ ${asciidoctor.outputDir} ์˜ ๊ฒฝ๋กœ๊ฐ€ ๋‹ค๋ฅธ๊ฒฝ์šฐ๋„ ์žˆ๋‹ค. ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์œ„์™€ ๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ outputDir์˜ ์œ„์น˜๋ฅผ ์ฐ์–ด๋ณด๊ณ  ์œ„์น˜๋ฅผ ๋ณ€๊ฒฝํ•ด์ค˜๋„ ์ข‹๋‹ค.

 

์—ฌ๊ธฐ๊นŒ์ง€๋Š” ํ•„์ˆ˜๋กœ ์ ์šฉํ•ด์•ผํ•˜๋Š” ๋ถ€๋ถ„์ด๊ณ , ๊ธฐ๋ณธ์ ์œผ๋กœ ์ด๋ ‡๊ฒŒ ์ž‘์„ฑ๋œ๋‹ค๋ฉด build/docs/ ๊ฒฝ๋กœ๋กœ html ํŒŒ์ผ์ด ์œ„์น˜ํ•˜๊ฒŒ ๋œ๋‹ค. ์ด๋ฅผ src/main/resources/static/docs ๋กœ ๋ณต์‚ฌํ•˜๋Š” ์ž‘์—…์€ ์•„๋ž˜ ์ „์ฒด ์ฝ”๋“œ์— ์ถ”๊ฐ€๋˜์–ด์žˆ๋‹ค.

 

[์ „์ฒด ์ฝ”๋“œ]

<bash />
/******* Start Spring Rest Docs *******/ ext { snippetsDir = file("$buildDir/generated-snippets") } test { useJUnitPlatform() outputs.dir snippetsDir } asciidoctor.doFirst { delete file("src/main/resources/static/docs") } asciidoctor { inputs.dir snippetsDir configurations 'asciidoctorExt' dependsOn test } bootJar { dependsOn asciidoctor from("${asciidoctor.outputDir}/html5") { into 'static/docs' } } task copyDocument(type: Copy) { dependsOn asciidoctor from file("$buildDir/docs/asciidoc") into file("src/main/resources/static/docs") } build { dependsOn copyDocument } /******* End Spring Rest Docs *******/

test -> asciidoctor -> copyDocument -> build ์ˆœ์œผ๋กœ ์ž‘์—…์ด ์ง„ํ–‰๋˜์–ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ‚ค๋ฉด http://localhost:8080/docs/ํŒŒ์ผ๋ช….html ๊ฒฝ๋กœ๋ฅผ ํ†ตํ•ด API ๋ฌธ์„œ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.

 

3.2. Adoc ์„ค์ •

<java />
= ALARM API ๋ฌธ์„œ :doctype: book :icons: font :source-highlighter: highlightjs :toc: left :toclevels: 3 == ์•Œ๋ฆผ ๊ฐ€์ ธ์˜ค๊ธฐ === Request include::{snippets}/alarm/postAlarm/http-request.adoc[] === Response include::{snippets}/alarm/postAlarm/http-response.adoc[]

asciidoc ๋ฌธ๋ฒ• ์ฐธ๊ณ 

 

index.adoc
์ „์ฒด ์ฝ”๋“œ๋ฅผ ์—ฐ๊ฒฐํ•ด์ค„ ํ™ˆ ํ™”๋ฉด์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค. include๋ฅผ ํ†ตํ•ด ๋‹ค๋ฅธ ํŒŒ์ผ์„ ์—ฐ๊ฒฐํ•ด์ฃผ๋ฉด ๋œ๋‹ค!

<java />
= Spring REST Docs Test :doctype: book :source-highlighter: highlightjs :toc: left :toclevels: 2 :seclinks: include::test.adoc[]

 

 

3.3. MockMvc ์„ค์ •

[ํ†ตํ•ฉํ…Œ์ŠคํŠธ ์‚ฌ์šฉ์‹œ]

<java />
@ExtendWith({RestDocumentationExtension.class}) @Transactional @SpringBootTest public class IntegrationTest { @Autowired protected WebApplicationContext webApplicationContext; protected MockMvc mockMvc; @BeforeEach protected void setUpAll(RestDocumentationContextProvider restDocumentationContextProvider) { this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) .addFilter(new CharacterEncodingFilter("UTF-8", true)) .apply(documentationConfiguration(restDocumentationContextProvider) .operationPreprocessors() .withRequestDefaults( // (1) modifyUris().scheme("https").host("docs.api.com").removePort(), prettyPrint()) .withResponseDefaults(prettyPrint()) // (2) ) .build(); } }

MockMvcBuilders๋ฅผ ํ†ตํ•ด MockMvc๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. ์„ค์ •์œผ๋กœ WebApplicationContext๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ์Šคํ”„๋ง์—์„œ ๋กœ๋“œํ•œ WebApplicationContext์˜ ์ธ์Šคํ„ด์Šค๋กœ ๋™์ž‘ํ•˜๊ธฐ์— ์ปจํŠธ๋กค๋Ÿฌ๋Š” ๋ฌผ๋ก  ์˜์กด์„ฑ๊นŒ์ง€ ๋กœ๋“œ๋˜์–ด ์™„์ „ํ•œ ํ†ตํ•ฉํ…Œ์ŠคํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค. ํ•„ํ„ฐ์— UTF-8์„ ๊ฐ•์ œํ•˜๋Š” CharacterEncodingFilter์„ ์ถ”๊ฐ€ํ•˜์—ฌ MockMvc ์‚ฌ์šฉ ์‹œ ํ•œ๊ธ€ ๊นจ์ง์„ ๋ฐฉ์ง€ํ•œ๋‹ค.

 RestDocument๋Š” ์š”์ฒญ๊ณผ ์‘๋‹ต์„ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋Š” ์ „์ฒ˜๋ฆฌ๊ธฐ๋ฅผ ์ œ๊ณตํ•œ๋‹ค.
(1) : ๋ฌธ์„œ์˜ Request URI๋ฅผ ๊ธฐ๋ณธ http://localhost:8080์—์„œ https://docs.api.com์œผ๋กœ  ๋ณ€๊ฒฝํ•˜๊ณ , ์˜ˆ์˜๊ฒŒ ์ถœ๋ ฅํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.
(2): ๋ฌธ์„œ์˜ Response๋ฅผ ์˜ˆ์˜๊ฒŒ ์ถœ๋ ฅํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.

 ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ํ…Œ์ŠคํŠธ๋Š” ์œ„์˜ ํด๋ž˜์Šค๋ฅผ ์ƒ์†๋ฐ›์•„์„œ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

 

 

[์ง์ ‘ MockMvc ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉ]

<java />
//Test @AutoConfigureRestDocs @WithMockUser @WebMvcTest(AlarmController.class) public class AlarmControllerTest { @Autowired private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; //DTO to Json @MockBean private AlarmService alarmService; //mocking @DisplayName("์•Œ๋žŒ ์ƒ์„ฑ") @Test void createAlarm() throws Exception { //given AlarmDto alarmDto = new AlarmDto("test12", "test22"); Alarm alarm = Alarm.builder() .alarmId(1L) .testContent1("test1") .testContent2("test2") .build(); given(alarmService.createAlarm(any(Alarm.class))) .willReturn(alarm); //when & then mockMvc.perform(MockMvcRequestBuilders.get("/alarm") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(alarmDto)) .with(SecurityMockMvcRequestPostProcessors.csrf())) .andDo(MockMvcResultHandlers.print()) .andDo(MockMvcRestDocumentation.document("alarm/postAlarm", Preprocessors.preprocessRequest(prettyPrint()), Preprocessors.preprocessResponse(prettyPrint()))) .andExpect(MockMvcResultMatchers.status().isCreated()); } }

 

<java />
//Controller @RestController @RequestMapping @RequiredArgsConstructor public class AlarmController { private final AlarmService alarmService; @GetMapping("/alarm") public ResponseEntity postAlarm(@RequestBody AlarmDto alarmDto) { Alarm alarm = Alarm.builder() .alarmId(1L) .testContent1("alarmDto.getTestContent1()") .testContent2("alarmDto.getTestContent2()") .build(); return new ResponseEntity<>(alarmService.createAlarm(alarm), HttpStatus.CREATED); } }

 

<java />
//DTO @Getter @Setter @AllArgsConstructor public class AlarmDto { private String testContent1; private String testContent2; }

 

<java />
//Entity @Entity @Builder @Getter public class Alarm { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long alarmId; private String testContent1; private String testContent2; }

 

<java />
//service -> ์ด๋ถ€๋ถ„๋„ Mocking ๋œ๋‹ค. @Service public class AlarmService { public Alarm createAlarm(Alarm alarm) { return Alarm.builder().alarmId(1L).testContent1("111").testContent2("222").build(); } }

 

 

3.4. ๊ฒฐ๊ณผ

์ด๋ ‡๊ฒŒ ํ‘œ์ถœ์ด ๋˜๊ฒŒ ๋œ๋‹ค.

 

 

 

4. ์ฐธ๊ณ 

RestDocs ๊ณต์‹๋ฌธ์„œ

๋ฐ˜์‘ํ˜•
profile

fee-fi-fo-fum

@hae02y

ํฌ์ŠคํŒ…์ด ์ข‹์•˜๋‹ค๋ฉด "์ข‹์•„์š”โค๏ธ" ๋˜๋Š” "๊ตฌ๋…๐Ÿ‘๐Ÿป" ํ•ด์ฃผ์„ธ์š”!