테스트 코드 작성 중 객체의 메모리 주소값 문제

대영·2024년 8월 3일
2

트러블 슈팅

목록 보기
5/5

🙏내용에 대한 피드백은 언제나 환영입니다!!🙏

🚨 문제가 발생한 코드

	@Test
    @DisplayName("게시글 업로드 성공 테스트")
    void savePostTest() throws Exception {
        // given
        PostReq postReq = new PostReq("제목", "내용");
		when(postService.savePost(postReq, userDetails.getId())).thenReturn(1L);

        // when
        ResultActions resultActions = mockMvc.perform(
                post("/posts")
                        .contentType(MediaType.APPLICATION_JSON)
                        .with(user(userDetails))
                        .content(objectMapper.writeValueAsString(postReq))
        );

        // then
        resultActions
                .andDo(print())
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.response").value(1L));
    }

🚨 트러블 내용

Controller에 대해서 Test코드를 작성 중, 문제가 발생하였다.
Mocking을 이용하기 위해 given, when을 사용해 특정 메서드를 실행하는 과정에서, 아래와 같이 코드를 작성하면 원하는 결과값이 나오지 않았다. (틀린 예시이다.)

// given
PostReq postReq = new PostReq("제목", "내용");
when(postService.savePost(postReq, userDetails.getId())).thenReturn(1L);

'1L'이 반환되게 해놓았기에 아래와 같이 결과값을 기대하면 제대로 실행이 되어야 한다.

// then
        resultActions
                .andDo(print())
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.response").value(1L));

하지만, 실행되는 결과는 1L이 아니다.

그 이유는, 실행은 되었지만, 가리키고 있는 메모리 주소가 다르기 때문이다.

이 말에 대한 정확한 내용은 아래에 설명하겠다.

❓ 왜 그런 것일까?

우선, Mocking을 이용한 테스트 방식은 '메모리 주소' 를 비교한다.
그러니깐, 테스트를 할 때, 객체같은 경우에도 같은 메모리 주소를 가져야한다.
그래서, 내가 작성한 코드에서는 동일한 메모리 주소를 가지지 않은 객체가 될 수 있다는 문제점이 발생하였다.

그래서 아래와 같은 부분에서 문제가 발생한 것이다.

// given
PostReq postReq = new PostReq("제목", "내용");
when(postService.savePost(postReq, userDetails.getId())).thenReturn(1L);

하지만, 나는 의문이 들었다.
직접 postReq를 만들었고, 그것을 넣어주는데 무엇이 문제인가?

문제가 발생하는 공간은 Controller에 작성한 api에서 발생한다.

아래와 같은 코드이다.

	@PostMapping("/posts")
    public ResponseEntity<?> savePost(@Valid @RequestBody PostReq postReq,
                                      @AuthenticationPrincipal SecurityUserDetails userDetails) {

        return ResponseEntity.status(HttpStatus.CREATED).body(postService.savePost(postReq, userDetails.getId()));
    }

여기서 보면 @Valid @RequestBody PostReq postReq 부분이 있는데, 이 부분에서 @RequestBody로 받는 과정에서 JSON에서 객체로 다시 역직렬화 되기 떄문에 다른 객체가 된다.

그렇기 때문에, 메모리 주소를 참조하는 Mocking 테스트에서 문제가 발생하는 것이다.

다른 메모리를 참조하기에 원하는 결과값이 나오지 않는 것이다.

👉 해결방법

Mockito에서의 인자 매처(Matcher)를 사용하는 것이다.
any(), eq() 등의 인자 매처가 있고, 이것이 의미하는 것은 아래와 같다.

  • any(PostReq.class): 어떤 PostReq 객체든 상관없이 일치한다는 뜻.
  • eq(userDetails.getId()): userDetails.getId()의 값과 정확히 일치하는 인자에 대해 일치한다는 뜻.

이 외에도 anyString() 등이 있다.

이것을 사용하게 되면, 어떤 객체든 간에 메모리를 참조하는 주소는 같아지기 때문에, 메모리 주소가 다르다는 문제를 해결할 수 있다.

여기서 조심해야할 것은 매처를 사용한다면 모든 파라미터에 매처를 사용해야 한다. any(PostReq.class)를 한다면, 똑같이 매처를 사용해서 eq(userDetails.getId())를 사용해야한다.

고친 부분은 아래와 같고,

when(postService.savePost(any(PostReq.class), eq(userDetails.getId()))).thenReturn(1L);

최종적인 코드는 아래와 같다.

	@Test
    @DisplayName("게시글 업로드 성공 테스트")
    void savePostTest() throws Exception {
        // given
        PostReq postReq = new PostReq("제목", "내용");
		when(postService.savePost(any(PostReq.class), eq(userDetails.getId()))).thenReturn(1L);

        // when
        ResultActions resultActions = mockMvc.perform(
                post("/posts")
                        .contentType(MediaType.APPLICATION_JSON)
                        .with(user(userDetails))
                        .content(objectMapper.writeValueAsString(postReq))
        );

        // then
        resultActions
                .andDo(print())
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.response").value(1L));
    }

++ 한 가지 방법이 더 있다. dto를 record로 사용하는 것이다.
레코드를 사용하면 대부분의 경우 매처가 필요하지 않을 수 있다. 그 이유는 레코드가 equals()와 hashCode() 메서드를 자동으로 생성해준다. 따라서 레코드 객체가 동일한 값으로 생성되었다면, 동일한 객체로 간주되어 Mockito의 동등성 비교에서 문제가 발생하지 않는다.

💡 느낀점

테스트를 위해서 api 플랫폼인 PostMan 등이 있지만, 이것을 사용할 수 없는 상황이 있을 수 있다. 예를 들자면, 로그인이 완전히 구현되지 않았는데, 로그인이 필요한 상황에서의 로직과 같은 상황이 있다면 말이다. 또한, 빠른 테스트와 초기에 문제를 확인하는 등을 위해서 단위, 통합테스트를 진행하는 것이 좋다. 또한, 테스트를 통한 확인은 코드의 안정성과 품질을 보장할 수 있는 점은 큰 장점이 있다.

이런 상황에서, 테스트 코드는 제대로 동작하는지 확인하기 위한 중요한 코드이다.

테스트 코드를 작성하며 위와 같은 문제를 맞이하였고, Mocking 테스트에 대한 이해도를 높일 수 있었다.

profile
Better than yesterday.

0개의 댓글