본문 바로가기
Spring/Test

[Spring] 테스트에서 PK 값으로 조회할 경우 문제

by 델버 2023. 1. 7.

문제

@Transaction rollback시 auto_increment는 rollback 되지 않는다.

// MemberServiceTest.class

@Transactional
@Test
public void 회원_가입() throws Exception {
    // given
    MemberSaveRequestDto dto = MemberSaveRequestDto.builder()
            .userName("delver")
            .email("delvering17@gmail.com")
            .picture("picture")
            .role(Role.USER)
            .joinRoot(JoinRoot.LOCAL)
            .build();

    // when
    memberService.save(dto);

    // then
    Member findMember = memberService.findById(1L);
    assertThat(findMember.getUserName()).isEqualTo("delver");
    assertThat(findMember.getEmail()).isEqualTo("delvering17@gmail.com");
    assertThat(findMember.getRole()).isEqualTo(Role.USER);
    assertThat(findMember.getJoinRoot()).isEqualTo(JoinRoot.LOCAL);
}
  • 회원가입 테스트에서 dto를 가지고 바로 Service에 넘겨주었다. 이때 PK 값을 리턴할 방법이 없어 then에서 엔티티를 1L를 잡고 진행했다. 이 테스트 하나를 실행했을 때는 문제가 없는데 전체를 돌려보니 문제가 되었다.

  • @Transaction을 붙였음에도 rollback 후 pk가 auto increment되는 것이었다.
  • 이후 확인해 보니 @Transaction은 auto increment자체가 롤백 된다는 것이 정합성에 문제가 되어 롤백 되지 않는다는 것을 찾았다.

결론

  • @Transaction시 auto_increment는 롤백되지 않는다.
  • 테스트에서 PK 값으로 조회하는 것은 위험하다.

해결

  • 내게 남은 문제는 테스트에서 어떻게 엔티티를 찾을 것인가다.

방법1. 테스트 순서를 지정하여 PK 값 자체에 의존하는 기존 방식을 유지한다.

방법2. findAll을 사용하여 전체 목록을 받은 다음 인덱스 0번을 가져온다. 또는 findByUserName을 사용하여 엔티티를 찾는다.

방법3. save(dto)를 할 때 리턴 값을 pk 값을 하도록 수정한다.

→ 요구사항을 userName이 중복이 없게 할 것이라 findByUserName()을 Repository와 Service에 만들어 넣어줬다. 테스트에서는 findById가 아닌 findByUserName으로 엔티티 조회하는 것으로 수정했다.

// MemberRepository.class

public Member findByUserName(String userName) {
      String sql = "select m from Member m where m.userName = :userName";
      Member member = em.createQuery(sql, Member.class)
              .setParameter("userName", userName)
              .getSingleResult();
      return member;
}

 

// MemberService.class

@Transactional(readOnly = true)
public Member findByUserName(String userName) {
    Member member;
    try {
         member = memberRepository.findByUserName(userName);

    } catch (EmptyResultDataAccessException e) {
        throw new IllegalStateException("회원 정보가 없습니다.", e);
    }

    return member;
}
  • em.find()에서 getResultList()가 아닌 getSingleResult()를 사용했다. 그런데 getResultList()와 달리 getSingleResult()는 값이 2개 이상이거나 아예 없어도 예외를 발생시킨다. 추후에 이런 예외를 MemberException을 만들어 처리할 것이므로 일단 NoResultException을 IllegalStateException로 변환시켰다.

 

// MemberServiceTest.class

@Transactional
@Test
public void 회원_정보_없으면_exception() throws Exception {
    assertThatThrownBy(() -> memberService.findByUserName("nobody"))
            .isInstanceOf(IllegalStateException.class);
    assertThatThrownBy(() -> memberService.findById(1L))
            .isInstanceOf(IllegalStateException.class);

}
  • 테스트는 이렇게 되었다. 생각해보니 이 테스트의 목적은 회원을 찾지 못하면 exception을 내는 것이다. 기존에 DTO save하는 부분이 필요없어서 제거하고, 조회하는 메서드의 예외만 확인하는 형태로 수정했다.

전체 테스트 코드

@SpringBootTest
class MemberServiceTest {
    @Autowired
    private MemberService memberService;

    @Transactional
    @Test
    public void 회원_가입() throws Exception {
        // given
        MemberSaveRequestDto dto = createMemberSaveRequestDto();

        // when
        memberService.save(dto);

        // then
        Member findMember = memberService.findByUserName(dto.getUserName());

        assertThat(findMember.getUserName()).isEqualTo("delver");
        assertThat(findMember.getEmail()).isEqualTo("delvering17@gmail.com");
        assertThat(findMember.getRole()).isEqualTo(Role.USER);
        assertThat(findMember.getJoinRoot()).isEqualTo(JoinRoot.LOCAL);
    }



    @Transactional
    @Test
    public void 회원_정보_없으면_exception() throws Exception {
        assertThatThrownBy(() -> memberService.findByUserName("nobody"))
                .isInstanceOf(IllegalStateException.class);
        assertThatThrownBy(() -> memberService.findById(1L))
                .isInstanceOf(IllegalStateException.class);

    }

    @Transactional
    @Test
    public void 회원_정보_수정() throws Exception {
        // given
        MemberSaveRequestDto saveDto = createMemberSaveRequestDto();
        memberService.save(saveDto);

        // when
        MemberUpdateRequestDto updateDto = MemberUpdateRequestDto.builder()
                .userName("aaaa")
                .email("aaaa@gmail.com")
                .picture("bbbb")
                .build();
        memberService.updateMember(1L, updateDto);

        // then
        Member updateMember = memberService.findByUserName(updateDto.getUserName());

        assertThat(updateMember.getUserName()).isEqualTo("aaaa");
        assertThat(updateMember.getEmail()).isEqualTo("aaaa@gmail.com");
        assertThat(updateMember.getPicture()).isEqualTo("bbbb");
    }

    private MemberSaveRequestDto createMemberSaveRequestDto() {
        return MemberSaveRequestDto.builder()
                .userName("delver")
                .email("delvering17@gmail.com")
                .picture("picture")
                .role(Role.USER)
                .joinRoot(JoinRoot.LOCAL)
                .build();
    }

}

 

- 이 외에 다른 방법이 있다면 댓글로 한 수 알려주세요!

댓글