GraphQlTester를 사용할 때 entity 변환이 안되는 이유(feat: ObjectMapper)

햄햄·2023년 4월 14일
0

문제

GraphQL API에 대한 Acceptance Test를 작성하기 위해 GraphQLTester를 사용했다. 내가 작성한 테스트는 대략 다음과 같다.

scenario("사용자가 인수를 전달하면 해당 조건에 부합되는 리뷰만 조회할 수 있다.") {
    val reviews = getReviews({특정 조건})
   reviews shouldBe intendedResult
}

private fun getReviews(variable: String): MutableList<Review> {
    val query = """{그래프큐엘 쿼리 with variable}"""
    
    return executeQuery(query, "{path}")
        .entityList(Review::class.java)
        .get()
}

private fun executeQuery(query: String, path: String): GraphQlTester.Path {
    return tester.mutate()
        .build()
        .document(query)
        .execute()
        .path(path)
}

Kotest FeatureSpec으로 작성한 테스트코드 일부에 수도 코드를 섞었다.

그런데 계속 테스트가 실패했다. 프로덕션 코드가 잘못된건 아닌 것 같은데 이상했다. 의아해서 reviews를 프린트해보니 Review 인스턴스들의 필드값이 전부 null로 찍혀있었다.

가정

오랫동안 이것 저것 찾아보며 검색해보니, ObjectMapper가 의심스러웠다. ObjectMapper는 내부적으로 reflection을 사용하는데, reflection은 생성자 함수가 없는 객체는 제대로 생성하지 못한다고 한다. 그리고 Review 클래스는 생성자는 없고 정적 팩토리 메소드만 존재하는 상태였다. 왠지 GraphQlTester의 entityList 메소드가 내부적으로 json을 entity로 변환할 때 ObjectMapper를 사용할것 같단 추측이 들었다.

해결

Review 클래스에 @AllArgsConstructor(access = AccessLevel.PRIVATE)라는 어노테이션을 붙이니 테스트가 정상적으로 동작했다!

다시 문제

그런데 어느 날 테스트를 돌려보니 또 다시 해당 테스트가 실패했다. 알고보니 함께 프로젝트를 하는 팀장님이 전반적으로 코드를 클린업하면서 @AllArgsConstructor(access = AccessLevel.PRIVATE)를 지우셨던 거였다.(이런 코드에는 주석을 남겨야 한다.) 그런데 나 또한 reflection을 올바르게 작동시키기 위해 프로덕션 코드에 어노테이션을 붙이는 것이 찜찜하기도 했다. 이참에 문제의 원인이 정말 ObjectMapper였는지, 어노테이션을 붙이지 않고 해결할 수 있는 방법이 없는지 한번 찾아보기로 하였다.

분석

슬프게도 GraphQlTester에 대한 자세한 가이드 문서가 없었다. 그래서 entityList 메소드의 내부 구현을 살펴보았다. entityList는 내부적으로 Encoder 객체와 Decoder 객체를 활용해서 인수로 들어온 객체를 encoding, decoding한다. Encoder와 Decoder의 구현 객체는 여러가지가 있는데, GraphQlTesterAutoConfiguration은 다음과 같이Jackson2JsonEnconderJackson2JsonDecoder를 등록한다.

그리고 스프링 공식문서를 보면 Jackson2Encoder와 Jasckson2Decoder는 ObejctMapper를 사용하는 것을 알 수 있다. 즉, 엔티티 매핑이 제대로 안되는 이유는 엔티티에 기본 생성자가 없고 GraphQlTester가 ObjectMapper를 사용하기 때문인게 맞았다.

고민...

결국 GraphQlTester 구성으로 문제를 해결하는 방법은 찾지 못했고, @AllArgsConstructor(access = AccessLevel.PRIVATE) 어노테이션을 다시 붙이는 방법이 제일 간단한 것 같다는 생각이 든다. 아니면 테스트 코드를 전면적으로 수정하는게 좋을 수도 있다. 출근해서 한번 물어봐야겠다.

후기

팀장님과 문제의 테스트에 관한 이야기를 나누었고, 팀장님은 Acceptance Test에서 이렇게 자세한 assertion을 하는 것 자체가 어색하다고 하였다. Acceptance Test에서는 API에서 ok가 떨어지는지 정도만 확인하고, 좀 더 자세한 테스트는 통합 테스트(서비스 테스트)로 옮기는게 좋을 것 같다고 하셨다.

profile
@Ktown4u 개발자

0개의 댓글