기존에는 테스트 결과를 문서화 하기 위해, Rest Docs에서 제공하는 FieldDescriptor를 다음과 같이 생성하였다.
그러다 보니 Field가 많으면 엄청난 양의 코드를 작성해야 하고 이 부분의 가독성이 떨어져 개선하고자 한다.
❌ 문제의 코드
restDocMockMvc.post("/test") {
jsonContent(request)
}.andExpect {
status { isOk() }
}.andDo {
handle(
MockMvcRestDocumentation.document(
"{class-name}/test-success",
requestFields(
fieldWithPath("name").description("이름").attributes(field(EXAMPLE, request.name))
),
responseFields(
fieldWithPath("hashValue").description("name의 hashCode 값"),
fieldWithPath("originalName").description("trim을 적용한 name 값")
)
)
)
}
🤦♂️ 해당 코드는 반복적이고, 코드가 잘 읽히지 않는다.
✔ 개선 코드
restDocMockMvc.post(targetUri) {
jsonContent(request)
}.andExpect {
status { isOk() }
}.andDo {
createDocument(
"test-success",
requestBody("name" type STRING description "이름" example request.name),
responseBody(
"hashValue" type NUMBER description "name의 hashCode 값",
"originalName" type STRING description "trim을 적용한 name 값"
)
)
}
방법
Kotlin에서는 infix function과 확장 함수를 제공한다. 해당 기능을 사용해서 코드를 개선할 생각이다.
💬 Infix Function
두 개의 객체 중간에 들어가게 되는 함수를 의미한다.
👀 예시
val monday: Pair<String, String> = "Monday" to "월요일"
◼ 위와 같이 to라는 infix function을 사용해서 Pair 객체를 생성할 수 있다.
💬 확장 함수
기존에 정의된 클래스에 함수를 추가하는 기능이다.
👀 예시
fun MutableList<Int>.swap(a: Int, b: Int){
val tmp = this[a]
this[a] = this[b]
this[b] = tmp
}
◼ 위와 같이 MutableList 클래스에 swap이라는 함수 기능을 추가할 수 있다.
개선 과정
✔ FieldDscriptor를 가지고 있는 class를 만든다.
class RestDocsField(
val descriptor: FieldDescriptor
) {
infix fun isOptional(value: Boolean): RestDocsField {
if (value) descriptor.optional()
return this
}
infix fun description(value: String): RestDocsField {
descriptor.description(value)
return this
}
infix fun example(value: String): RestDocsField {
descriptor.attributes(field(EXAMPLE, value))
return this
}
}
◼ RestDocsField 클래스에서 FieldDescriptor를 받는다.
◼ infix 함수를 통해 FieldDescriptor에 해당 값을 추가한다.
✔ String 타입에 확장 함수 추가
infix fun String.type(
type: JsonFieldType
): RestDocsField = createField(this, type)
private fun createField(
path: String,
type: JsonFieldType
): RestDocsField = RestDocsField(PayloadDocumentation.fieldWithPath(path).type(type))
◼ String 클래스에 type이라는 infix 함수를 추가한다.
◼ JsonFieldType은 Rest Docs에서 제공하는 class로 필드의 type 지정할 수 있다.
"code" type JsonFieldType.NUMBER
◼ 위와 같이 코드를 작성하면, type 이라는 infix 함수에 "code" 가 this로, JsonFieldType.NUMBER가 type 인자로 들어간다.
◼ createField 함수를 통해 path에 code를 type에는 JsonFieldType.NUMBER가 저장되어 FieldDescriptor 객체가 생성되고 해당 객체를 인자로 받는 RestDocsField 객체가 반환된다.
"code" type JsonFieldType.NUMBER description "응답 코드"
◼ 위와 같이 코드를 작성하면, "code" type JsonFieldType.NUMBER 가 RestDocsField로 변환된다.
◼ 이후, 생성된 ResDocsField의 description infix 함수를 통해 FieldDescriptor 의 description 값으로 "응답 코드" 가 저장된다.
✔ ResponseFieldsSnippet 만들기
fun responseBody(vararg fields: RestDocsField): ResponseFieldsSnippet =
PayloadDocumentation.responseFields(fields.map { it.descriptor })
◼ 이제 생성된 RestDocsField를 인자로 받아 responseFields 함수에 RestDocsField의 descriptor를 인자로 넘겨주면 완성된다.
💬 vararg 키워드
가변 인자를 사용하면 함수를 호출할 때, 인자의 개수를 유동적으로 지정할 수 있다.
✔ MockMvcResultHandlersDsl에 확장 함수 추가
fun MockMvcResultHandlersDsl.createDocument(identifier: String, vararg snippets: Snippet) {
return handle(MockMvcRestDocumentation.document("{class-name}/$identifier", *snippets))
}
◼ createDocument로 snippet을 저장할 폴더 이름과 만든 snippets을 인자로 받아 anDo 함수를 실행한다.
결론
코틀린에서 제공하는 다양한 기능을 활용하여 기존 코드를 개선할 수 있었다. 반복적인 코드를 최소한으로 줄이고 가독성을 높여 작성하기 쉽고 이해하기 쉬운 코드를 작성할 수 있었다.
'Backend > Spring' 카테고리의 다른 글
Spring Event 사용하기 (0) | 2023.06.29 |
---|---|
kotlin + HttpInterface 알아보기 (0) | 2023.06.22 |
Kotest를 통한 DCI 패턴 적용 (0) | 2023.05.27 |
spring에서 flyway 사용하기 (0) | 2023.05.26 |
[Spring] checkstyle 적용하기 (0) | 2023.05.03 |