auto-increment로 인한 Controller 테스트 실패

MinSeong Kang·2022년 10월 21일
0

이슈해결

목록 보기
12/12

MockMvc 객체를 이용하여 Controller 테스트를 작성해보았다. 각각의 컨트롤러에 대해서 테스트 코드를 작성하고 테스트를 돌려보고, 테스트 코드 작성하고 돌려보고를 반복하였을 때는 모든 테스트 코드가 잘 돌아갔다.

문제상황..!

하지만, 모든 테스트 코드를 한번에 돌렸을 때, 아래와 같이 몇개만 맞고 나머지는 모두 실패했다.

우선 테스트 코드는 다음과 같다. @BeforeEach를 통해 매 테스트 코드가 테스트 DB에 10개의 Post를 저장해주었다.
해당 클래스에 @Transactional을 걸어주었기 때문에, @BeforeEach도 매 테스트마다 롤백이 일어나 모든 테스트 케이스는 10개의 데이터로 테스트를 진행할 수 있다.

@BeforeEach
void beforeEach() {
    Member member = memberRepository.save(new Member(1L, "test_username", passwordEncoder.encode("1234"),
            "test_nickname", "test@email.com", 3L, LocalDateTime.now()));

    List<Post> postList = new ArrayList<>();

    for (int i = 1; i <= 10; i++) {
        postList.add(new Post((long) i, member, "subject %s".formatted(i),
                "content %s".formatted(i), "contentHtml %s".formatted(i)));
    }

    postRepository.saveAll(postList);
}

아래와 같이 1L로 Post를 찾으면, 테스트 케이스가 만족할 것 같았다.. 해당 테스트 케이스만 실행했을 때는 성공이었다.

@Test
@DisplayName("글수정")
@WithMockUser(username = "test_username", password = "1234", roles = "USER")
void modify() throws Exception {
    mockMvc.perform(post("/post/1/modify")
                    .param("subject", "modify subject")
                    .param("content", "modify content")
                    .param("postKeywordContents", "#key1 #key2"))
            .andExpect(status().is3xxRedirection())
            .andExpect(handler().handlerType(PostController.class))
            .andExpect(handler().methodName("modify"))
            .andExpect(redirectedUrlPattern("/post/**"));

    Post findPost = postRepository.findById(1L).orElseThrow();

    assertThat(findPost.getSubject()).isEqualTo("modify subject");
    assertThat(findPost.getContent()).isEqualTo("modify content");
}

하지만 왜 모든 테스트 케이스를 한번에 돌렸을 때, 해당 테스트 케이스는 실패가 됐을까??

이유는 @BeforeEach에서 데이터를 10개씩 저장 되지만, Post의 Id는 Auto Increament로 인해 Id는 초기화 되지 않는다.
즉, 해당 테스트 케이스는 첫번째로만 실행이 되어야 성공되는 테스트 케이스이고, 2번째는 데이터베이스에 11~20의 id를 가지는 Post가 저장되기 때문에 매번 id가 1인 Post에 대해서 찾기 때문에 "해당 글이 존재하지 않는다" 예외가 발생한 것이다.

해결 방법은??

1) Pagable 이용하기

@Test
@DisplayName("글수정")
@WithMockUser(username = "test_username", password = "1234", roles = "USER")
void modify() throws Exception {
    Page<Post> page = postRepository.findAll(PageRequest.of(0, 1));
    Long id = page.getContent().get(0).getId();

    mockMvc.perform(post("/post/%s/modify".formatted(id))
                    .param("subject", "modify subject")
                    .param("content", "modify content")
                    .param("postKeywordContents", "#key1 #key2"))
            .andExpect(status().is3xxRedirection())
            .andExpect(handler().handlerType(PostController.class))
            .andExpect(handler().methodName("modify"))
            .andExpect(redirectedUrlPattern("/post/**"));

    Post findPost = postRepository.findById(id).orElseThrow();

    assertThat(findPost.getSubject()).isEqualTo("modify subject");
    assertThat(findPost.getContent()).isEqualTo("modify content");
}
  • 다음과 같이 테스트 코드를 실행하기 전에, Page<Post> page = postRepository.findAll(PageRequest.of(0, 1)); 로 데이터베이스에 저장된 첫번째 데이터를 가져온 후 해당 Post의 Id 값을 가져왔다.
  • 이후 id값이 들어가야하는 부분에 찾아온 Id 값을 넣어주어 해결했다.
  • 해당 방법은 매번 반복적인 코드와 찾아온 Id를 직접 넣어주어야 하는 번거로움이 있었다.

구글링을 해본 결과,

2) auto-increment 값을 재지정

@AfterEach
void afterEach() {
    this.entityManager
            .createNativeQuery("ALTER TABLE post ALTER COLUMN `id` RESTART WITH 1")
            .executeUpdate();
}
  • 다음과 같이 매번 테스트가 끝날 때마다, auto-increment를 재저정해주는 것이다.
  • 이를 통해 @BeforeEach를 통해 데이터베이스에 저장되는 데이터의 Id는 1부터 시작되는 것을 보장한다.

하지만 해당 방법에는 제약 사항이 존재한다.

  • @DataJpaTest 등 @Transactional이 적용되는 서비스 클래스를 테스트할 때는 적용 가능하지만 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)를 사용하는 통합 테스트에서는 사용할 수 없다.

처음에는 Pagable를 사용하여 테스트 코드를 작성하였는데, 너무 반복적인 코드 작성과 각각의 테스트가 서로 의존이 되는 것 같아 2번째의 방법을 선택하여 테스트 코드를 작성하였다.

참고자료

https://github.com/HomoEfficio/dev-tips/blob/master/Spring%20Data%20JPA%20테스트%20시%20auto-increment%20문제.md

0개의 댓글