TIL_029 | Describe Spec Test Code

묘한묘랑·2024년 2월 10일
0

TIL

목록 보기
29/31

오늘 할 이야기는 이전 글의 연장선에 가까운 이야기이다.


우선, 이전글에서는 it에 모든 것을 몰아주는 형태였는데, Describe Spec과는 거리가 멀어보여 다시 refactoring 함과 동시에, 한글이 깨지는 상황과 배열에 대한 처리 방법에 대하여 글을 적어보려 한다.

1. 변경점

우선 이 이야기를 하지 않고 넘어갈 수가 없는데, 현재 작성하고있는 Kotest에서 Describe Spec을 사용할 때 이것을 Describe 부분을 완전히 때어내어도 무방한 상황이긴 하다. 분류를 위한 글 그 이상 그 이하도 될 수가 없는 상황이기에 이것을 어떻게 해야 할지에 대한 고민이 필요한 단계인 것 같다.

@AutoConfigureMockMvc(addFilters = false)
@ExtendWith(MockKExtension::class)
@WebMvcTest(controllers = [PostController::class])
class PostControllerTest @Autowired constructor(
    private val mockMvc: MockMvc,
) : DescribeSpec() {
    @MockkBean
    private lateinit var postApiService: PostApiService


    init {
        extensions(SpringExtension)

        afterContainer { clearAllMocks() }

        describe("GET /post/{postId}는") {
            context("존재하는 ID를 요청할 때") {
                val postId = 1L

                every { postApiService.getPostByPostId(any()) } returns PostRes(
                    postId = postId,
                    title = "Test Title",
                    tagList = listOf("태", "그", "리", "스", "트"),
                    description = "test descript",
                    writer = "test writer",
                    createdAt = LocalDateTime.now()
                )

                val result = mockMvc.perform(
                    get("/post/$postId")
                        .contentType(MediaType.APPLICATION_JSON)
                        .accept(MediaType.APPLICATION_JSON_UTF8)
                ).andReturn()

                val content = JSONObject(result.response.contentAsString)
                
                it("200 status code를 응답해야한다.") {
                    result.response.status shouldBe 200
                }
                it("Content는 postId에 맞게 제대로 가져와야 한다."){
                    content.get("id") shouldBe postId
                }
                it("tagList는 배열로 가져와야 하며, 한글이 깨져서는 안된다."){
                    val tagList = content.get("tagList") as JSONArray
                    tagList[0] shouldBe "태"
                }
            }

            context("존재하지 않는 ID를 요청할 때") {
                val postId = 0L

                every { postApiService.getPostByPostId(any()) } throws NotFoundTargetException("$postId Post를 찾을 수 없습니다.")
                
                val result = mockMvc.perform(
                    get("/post/${postId}")
                        .contentType(MediaType.APPLICATION_JSON)
                        .accept(MediaType.APPLICATION_JSON_UTF8)
                ).andReturn()

                val content = JSONObject(result.response.contentAsString)
                
                it("NotFoundTargetException을 받환한다.") {
                    content.get("errorCode") shouldBe ErrorCode.NOTFOUND_ENTITY_ERROR.code
                }
            }
        }
    }
}

이전 글에서 달라진것은 every와 postId, 실행 부분이 context로 올라갔고, jacksonObjectMapper가 아닌 JSONObject를 사용하고 있다.
또한 accept의 Context-Type을 UTF8로 바뀐 것과, 배열을 처리하기 위하여 tagList를 JSONArray로 casting 시켜 테스트 하기 위해서이다.

1.1 Describe Sepc

사실 현재 Describe Spec에서 Describe는 그냥 분류 그 이상도 그 이하도 아닌 상황이다.

현재 Describe에 들어가야만 하는 친구가 있는데, 그 친구는 현재 Describe의 바깥에 존재를 하고 있다.
그것이 무엇이냐 묻는다면...

@MockkBean
private lateinit var postApiService: PostApiService

mocking된 Service인 postApiService가 완전 바깥으로 나와있는 상황이다.

내가 생각했을 때의 Describe Spec에서는 이 mock 객체가 describe 안으로 들어가야하는 상황이다.

Describe(){
	@MockkBean
    val postApiService: PostApiService
    context(){
    	it(){}
    }
}

위와 같은 형태로 말이다.

이렇게 생각하게 된 이유는 우선, Describe안에 있지 않는다면, Describe 별로 Test를 진행할 때, 필요치 않은 mock 객체까지 만들어 질 수 있다는 단점이 생긴다.

하지만 현재 kotest로 진행할 때, mockMvc.perform을 사용하기 위해서는, mock객체를 밖으로 내지 않으면 사용할 수 없는 상황이다.
이것을 토대로 나는 kotest를 유지한다고 하면 mockMvc가 아닌 library를 선택하여 테스트를 진행해야 할 것 같다. 이것을 가능케 해주는 library가 존재치 않다면 이 형태의 테스트 보다도 다른 형태의 Test Code를 작성하게 될 것 같다.

1.2 JSONObject를 사용하게 된 이유

이전 테스트에서는 jacksonObjectMapper를 사용하였는데 그 때는 createdAt이라는 시간과 관련된 property가 없던 상황 이었다.
추가가 된 이후 테스트를 진행하면 jacksonObjectMapper를 사용할 수 없는데, 만약ObjectMapper를 사용하게 된다면 dto에 추가적인 작업이 가해질 수도 있는 상황이었기에, 나는 테스트를 위하여 객체를 조작하는 것은 주객전도라고 생각하였기에 2가지 모두를 그냥 포기하고 JSONObject를 사용하는 것으로 결정하였다.
get을 통하여 가져오고, Type에 맞게 casting이 필요하였지만, 주객전도의 상황보다는 낫다고 판단하여 사용을 결정하였다. 또한, accept 쪽의 content-type을 application/json으로 사용하고 있던 상황이었기에 한글이 깨지고 있었다. 그리하여 application/json;charset=UTF-8로 바꾸어 한글이 깨지지 않도록 하였다.


추가적으로 나는 ErrorCode는 FrontEnd와의 협업 과정에서 가장 빛을 내는 친구라고 생각했지만, TestCode를 작성 할 때도 Error Code는 빛을 내고 있었다.

만약 Message나, 반환 객체를 기반으로 하였다면 각자 해당하는 비교를 만들어 주어야 하지만, ErrorCode 하나 만으로 해결이 가능하였다.

물론 반환되는 payload를 비교해야 하는 테스트일 경우에는 추가적인 작업이 필요하지만, 해당하는 Error가 터지는 것만 확인하는 작업이라면 ErrorCode가 빛을 낸다고 생각한다!


다음 글은 아마 글 검색을 요청 할 때 현재 배열을 queryString으로 주어야 하는상황을 어떻게 파훼할 것인지, Multipart/fomr-data 요청을 할 때 Back과 Front에서 어떻게 처리해야 하는 지에 대한 글을 적을 것 같다.

아니면 또 다른 무언가....

profile
상황에 맞는 기술을 떠올리고 사용할 수 있는 개발자가 되고 싶은 개발자

0개의 댓글