BackEnd๐Ÿงต

[RESTDocs] Spring RestDocs ์ ์šฉํ•˜๊ธฐ

hae02y 2023. 11. 10. 22:17
๋ฐ˜์‘ํ˜•

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

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

REST Docs? 

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

 

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

 

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

 

REST Docs vs Swagger

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

 

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

 

MockMvc vs RestAssured

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

 

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

 

 

REST Docs ์‚ฌ์šฉ

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

  • Java 11
  • Gradle 8.8
  • Spring 2.7

Gradle ์„ค์ •

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

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 ๋ฒ„์ „์„ ์‚ฌ์šฉํ•œ๋‹ค.

 

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

configurations {
    // ...

    asciidoctorExt
}

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

 

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

dependencies {
    // ...

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

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

 

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

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

 

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

test {
    useJUnitPlatform()
    outputs.dir snippetsDir
}

asciidoctor {
    inputs.dir snippetsDir
    configurations 'asciidoctorExt'
    dependsOn test
}

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

 

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

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

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

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

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

 

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

 

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

/******* 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 ๋ฌธ์„œ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.

 

Adoc ์„ค์ •

= 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๋ฅผ ํ†ตํ•ด ๋‹ค๋ฅธ ํŒŒ์ผ์„ ์—ฐ๊ฒฐํ•ด์ฃผ๋ฉด ๋œ๋‹ค!

= Spring REST Docs Test
:doctype: book
:source-highlighter: highlightjs
:toc: left
:toclevels: 2
:seclinks:

include::test.adoc[]

 

 

MockMvc ์„ค์ •

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

@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 ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉ]

//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());
    }
}

 

//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);
    }
}

 

//DTO

@Getter
@Setter
@AllArgsConstructor
public class AlarmDto {

        private String testContent1;

        private String testContent2;
}

 

//Entity

@Entity
@Builder
@Getter
public class Alarm {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long alarmId;

    private String testContent1;

    private String testContent2;
}

 

//service -> ์ด๋ถ€๋ถ„๋„ Mocking ๋œ๋‹ค.

@Service
public class AlarmService {

    public Alarm createAlarm(Alarm alarm) {

        return Alarm.builder().alarmId(1L).testContent1("111").testContent2("222").build();
    }
}

 

 

๊ฒฐ๊ณผ

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

 

 

 

์ฐธ๊ณ 

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

๋ฐ˜์‘ํ˜•