문제
- 게시판 프로젝트를 하고 있는 중에 테스트 작성 문제가 생겼다.
@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자체가 롤백 된다는 것이 정합성에 문제가 되어 롤백 되지 않는다는 것을 찾았다.
- 이와 관련된 다른 분의 좋은 글: https://wisdom-and-record.tistory.com/135
결론
- @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();
}
}
- 이 외에 다른 방법이 있다면 댓글로 한 수 알려주세요!
댓글