테스트에 @Transactional을 붙였다. 회원을 저장하고 테스트가 끝났으니 깨끗하게 사라졌을 거라 생각했다. 그런데 다음 테스트에서 같은 회원이 나온다. 애노테이션이 제 일을 안 한 걸까? 저장한 코드가 테스트의 트랜잭션 안에서 실행됐는지부터 확인해 보자.
@Transactional 테스트는 무엇을 롤백할까?
스프링 테스트에서 다음처럼 테스트 클래스나 메서드에 @Transactional을 붙이면, 각 테스트 메서드에 테스트용 트랜잭션이 시작되고 기본적으로 끝에서 롤백된다. 같은 트랜잭션에 참여한 저장은 되돌아간다.
@SpringBootTest
@Transactional
class MemberRepositoryTest {
@Autowired MemberRepository members;
@Test
void saveMember() {
members.save(new Member("Ana"));
assertThat(members.count()).isEqualTo(1);
}
}이 예제는 빈 테스트 DB에서 시작하고, 저장 호출이 테스트 트랜잭션에 참여한다는 전제다. 테스트 안에서는 count()가 1을 볼 수 있다. 테스트가 끝나 롤백된 뒤에는 그 저장으로 추가된 행이 남지 않는다. @SpringBootTest만 붙인 경우에는 이 자동 롤백을 기대할 수 없다.
그림에서 갈리는 것은 테스트의 성공 여부가 아니라 트랜잭션이 실행되는 경계다. SQL의 BEGIN → INSERT → ROLLBACK에서는 행 수가 0 → 1 → 0, BEGIN → INSERT → COMMIT에서는 0 → 1 → 1로 남는다. 애노테이션은 이 중 어느 경계에 저장이 들어갔는지를 바꾸어 주지 않는다.
실제 포트로 보낸 HTTP 요청은 왜 롤백되지 않을까?
이번에는 테스트가 실제 서버 포트에 HTTP 요청을 보낸다고 해 보자.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Transactional
class MemberApiTest {
@Autowired TestRestTemplate http;
@Test
void createMember() {
http.postForEntity("/members", new CreateMemberRequest("Ana"), Void.class);
}
}TestRestTemplate 호출은 테스트 스레드 밖의 실제 서버로 간다. 서버가 요청을 처리하며 시작한 트랜잭션은 테스트 메서드의 트랜잭션과 다르다. 따라서 서버에서 저장이 커밋됐다면, 테스트 쪽 롤백은 그 행을 되돌리지 못한다. Spring Boot의 테스트 문서도 실제 포트를 쓰는 테스트의 클라이언트·서버 트랜잭션 분리를 명시한다.
여기서 MemberRepositoryTest와 MemberApiTest는 비교를 위한 최소 예시다. Member, 요청 DTO, API와 저장소 구현은 프로젝트에 맞게 준비해야 한다. 이 코드만 붙여 넣어 실행되는 완성 프로젝트라고 생각하면 한 번 더 멈칫하게 된다.
테스트 데이터가 남았을 때 어디부터 확인할까?
먼저 테스트가 저장소를 직접 호출했는지, 실제 HTTP 포트로 요청했는지 구분한다. 그다음 @Transactional이 테스트 클래스·메서드 중 어디에 있는지 확인한다. @Rollback(false)나 @Commit을 붙였다면 기본 롤백도 달라진다.
실제 HTTP 통합 테스트라면 전용 테스트 DB에서 각 테스트의 입력을 격리하고, 명시적인 준비·정리 절차를 둔다. 테스트 트랜잭션 안의 @BeforeEach에서 삭제한 것까지 롤백될 수 있으므로, 이미 커밋된 서버 데이터를 지우려는 정리를 테스트 롤백에 무작정 맡기지 않는다. 테스트를 병렬로 실행한다면 공용 행을 서로 지우지 않도록 데이터 키와 실행 범위도 분리한다.
MockMvc로 애플리케이션을 호출하는 테스트와 실제 포트로 요청하는 테스트를 같은 것으로 취급하지 않는 것도 중요하다. 어느 쪽이든 저장 호출이 테스트가 관리하는 트랜잭션에 실제로 참여했는가를 확인해야 한다. ‘테스트가 초록색이었다’는 데이터가 격리됐다는 증거는 아니다.
핵심 요약
@Transactional 테스트는 테스트가 관리하는 트랜잭션을 기본적으로 롤백한다. 저장이 그 경계 안에 있다면 되돌아가지만, RANDOM_PORT의 실제 HTTP 서버가 별도 트랜잭션에서 커밋한 데이터까지 지우지는 않는다. 데이터가 남으면 애노테이션부터 늘리기보다 요청 경로 → 트랜잭션 경계 → 정리 방식 순서로 확인하자.
작성자
기초 개념을 구현과 검증, 실제 운영 판단까지 연결해 기록합니다.

