Skip to content

[테스트 코드] RestDocs 사용에 따른 Controller 단위 테스트 ( e2e Test )

이은비 edited this page Mar 29, 2023 · 1 revision

RestDocs 사용에 따른 테스트코드 작성 과정에 대한 내용을 정리하고자 합니다.

RestDocs를 위한 세팅 과정

RestDocs 사용을 위해서는 build.gradle.kt 파일에 dependency를 추가해주어야 합니다. 아래 파일은 RestDocs가 적용된 부분만을 발췌한 코드입니다. ( 전체 코드가 궁금하시면, 여기를 참고해주세요. )

[build.gradle.kt]

...

dependencies {
    ...
    // 테스트를 위한 의존성을 아래 3가지 추가해주세요.
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.springframework.restdocs:spring-restdocs-mockmvc")
    testImplementation("org.springframework.restdocs:spring-restdocs-asciidoctor")
}

// asciidoctor 라이브러리를 이용해 문서화를 진행하기 때문에 해당 의존성을 추가해줍니다.
val asciidoctorExt: Configuration by configurations.creating
dependencies {
    asciidoctorExt("org.springframework.restdocs:spring-restdocs-asciidoctor")
}

// snippet 형태로 만들어지기 때문에 해당 파일이 위치할 곳을 지정합니다.
val snippetsDir by extra { file("build/generated-snippets")}
tasks {
    test {
        extensions.configure(JacocoTaskExtension::class) {
            destinationFile = file("$buildDir/jacoco/jacoco.exec")
        }
        finalizedBy(jacocoTestReport)
        // 경로를 세팅해줍니다.
        outputs.dir(snippetsDir)
    }
    // asciidoctor 경로 설정과 이 파일이 만들어지기 위해서는 test 작업이 선행되어야 함을 명시해줍니다.
    asciidoctor {
        inputs.dir(snippetsDir)
        configurations(asciidoctorExt.name)
        dependsOn(test)
        doLast {
           // 백업을 위해 복사본을 항상 resources 파일에 생성 만들도록 합니다. ( 이 과정은 선택사항입니다. )
            copy {
                from("build/docs/asciidoc")
                into("src/main/resources/static/docs")
            }
        }
    }
    // 빌드 시, asciidoctor 설정 과정이 선행되어야 함을 명시해줍니다.
    build {
        dependsOn(asciidoctor)
    }
}

...

위의 코드가 의미하는 바는 주석으로 표기했습니다. 추가적으로 궁금한 점이 있다면 공식문서를 확인해주세요.

이렇게 build와 test 시, 설정되어야 하는 값을 작성했다면 코드 상에서 어떤 식으로 작성해야 하는 지에 대해 알아보도록 하겠습니다.

[ApiControllerConfig.kt]

RestDocs를 이용해 문서화를 하기 위해서는 Controller 단위의 테스트가 선행되어야 합니다. 이때 사용되는 것이 MockMvc 입니다. 웹에서 실제로 요청이 오고가는 것처럼 Mocking해서 테스트를 진행하기 때문에, 이를 위한 설정이 필요합니다. 아래 코드는 그 설정을 글로벌하게 관리하여 중복되는 코드를 방지하기 위한 클래스입니다.

@AutoConfigureRestDocs
@AutoConfigureMockMvc
@SpringBootTest
@ExtendWith(RestDocumentationExtension::class)
class ApiControllerConfig constructor(
    protected val uri : String?
){
    @Autowired
    protected lateinit var mockMvc: MockMvc

    @Autowired
    protected lateinit var objectMapper: ObjectMapper

    @BeforeEach
    internal fun setUp(
        webApplicationContext: WebApplicationContext,
        restDocumentationContextProvider: RestDocumentationContextProvider
    ) {
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
            .addFilter<DefaultMockMvcBuilder>(CharacterEncodingFilter("UTF-8", true))
            .apply<DefaultMockMvcBuilder>(
                MockMvcRestDocumentation.documentationConfiguration(restDocumentationContextProvider)
                .operationPreprocessors()
                .withRequestDefaults(Preprocessors.prettyPrint())
                .withResponseDefaults(Preprocessors.prettyPrint()))
            .build()
    }
}

Controller 마다 고유의 url이 있기 때문에 이를 멤버 변수로 두었습니다. 해당 변수는 각 Controller 테스트에서 사용되기 때문에 둔 것이니 필수적인 사항은 아닙니다. 앞서 설명드렸듯이, 웹에서 요청이 오고 가는 것처럼 Mocking 해서 테스트하기 위해서는 MockMvc를 사용해야 하며, 이를 위한 설정이 필요합니다. 해당 작업은 @BeforeEach 어노테이션을 이용해 테스트가 진행될 때마다 먼저 실행되도록 하는 함수를 내부에 구현하였습니다.

위의 클래스는 아래와 같이 상속 받아 사용됩니다.

image

이렇게 함으로써 중복되는 코드를 방지할 수 있었습니다.

[MockEntity.kt]

image

위의 클래스는 테스트에 사용되는 더미 데이터만을 관리하는 클래스입니다. 게시글 생성 혹은 삭제 등과 같이 사용자가 필요로 하는 API들은 로그인 프로세스가 선행됩니다. 로그인에 사용되는 테스트 사용자 데이터를 미리 정의하여 필요로 하는 API들에 공통적으로 사용하기 위해 companion object를 활용했습니다.

코틀린에서 compainon object 키워드는 자바의 static 역할을 해줍니다. 하나의 클래스에 하나의 companion object 를 생성할 수 있으며, 이는 "객체"이자 별다른 정의 없이 싱글톤으로 구현됩니다.

compainon object 키워드를 통해 익명 객체 안에 함수들을 정의하면, 내부에 정의된 함수에 대해 외부에서 접근이 가능해집니다. 위의 MockEntity에 정의된 더미 데이터는 아래와 같이 사용됩니다.

image

이렇게 함으로써 중복되는 코드를 줄이고, DTO의 변경에도 MockEntity에 정의된 데이터만을 수정하면 되기에 유지보수 측면에서도 편리성을 확보할 수 있었습니다.

Clone this wiki locally