테스트코드를 작성하지 않고 손수 인수테스트를 하며 개발하는 동안 UserPart 또는 UserStack에서 논리 상으로는 제거가 되어야 하는데 제거되지 않은 데이터를 발견한 적이 있다. 중간에 잘못된 메소드를 실행했다가 그 결과가 났는 지 아니면 현재 메소드들도 어딘가에 문제가 있는 지 알 수가 없었다. 이래서 테스트 코드를 작성하면서 개발해야 한다.
아무튼 이런 찝찝한 기분을 받았기 때문에 jpa의 orphanRemoval이 잘 작동해 db에서 데이터가 삭제되는 지 확인하고 싶었다. 그래서 나름 테스트코드를 짜봤다.
@Transactional
public void modifyUsingStacks(Part part, List<Stack> stacks) {
// 초기화 시킨 뒤 다시 채우기
part.getPartStacks().clear();
part.addStacks(stacks);
}
@DataJpaTest
class PartServiceIntegrationTest {
@InjectMocks
PartService partService;
@Spy
PartRepository partRepository;
@Autowired
TestEntityManager testEntityManager;
@Test
@DisplayName("modifyStacks 호출 시 기존에 있던 연관된 PartStack은 db에서 전부 제거됨")
void whenModifyStacks_removePreviousPartStack() {
// given
User user = new User("test", "test", "test", Position.FRONTEND.name());
testEntityManager.persist(user);
Project project = new Project(user, "test", "test");
testEntityManager.persist(project);
FrontendPart part = new FrontendPart(project);
Stack stack1 = new Stack("test", "test");
Stack stack2 = new Stack("test2", "test2");
testEntityManager.persist(stack1);
testEntityManager.persist(stack2);
Part persistedPart = testEntityManager.persist(part);
part.addStacks(List.of(stack1, stack2));
Stack addingStack = new Stack("new", "new");
testEntityManager.persist(addingStack);
testEntityManager.flush();
// when
partService.modifyUsingStacks(persistedPart, List.of(addingStack));
// testEntityManager.flush();
// then
List<PartStack> partStacks = persistedPart.getPartStacks();
for (PartStack partStack : partStacks) {
System.out.println("partStack = " + partStack.getStack().getName());
}
assertThat(partStacks.size()).isEqualTo(1);
assertThat(partStacks.get(0).getStack().getName()).isEqualTo("new");
// PartStack 테이블에서도 제거됐어야 함
PartStack result1 = testEntityManager.find(PartStack.class, 1L);
PartStack result2 = testEntityManager.find(PartStack.class, 2L);
PartStack result3 = testEntityManager.find(PartStack.class, 3L);
assertThat(result1).isNull();
assertThat(result2).isNull();
assertThat(result3).isNotNull();
assertThat(result3.getStack().getName()).isEqualTo("new");
}
}
문제는 when 절의 flush()를 직접 해주지 않으면 orphanRemoval에 의해 고아가 된 객체들은 db에서 제거될 것이라는 기대와는 다르게 delete문이 발생하지 않았다. 실제 애플리케이션을 켜고 저 메소드를 호출하는 api를 사용하면 정상적으로 db에서 제거가 되었다. flush()를 비즈니스 로직에서 쓰지 않았는데 말이다. 저 주석 처리 된 flush()를 실행해서 테스트를 통과한다 한들 실제 애플리케이션에서 정상 작동한다는 보장이 없으므로 저 테스트코드는 쓸모가 없는 게 분명했다.
폭풍 stack overflow 검색을 해봤는데 스프링 애플리케이션에서 빈과 함께 작동할 때랑 테스트코드에서 작동할 때의 Transactional 작동 흐름이 다르다. 그래서 이러한 경우를 테스트하고자 할 때는 api에 request를 보내고 db에 정상적으로 반영되는 지를 테스트하는 통합 테스트를 작성할 것을 권장하고 있다.
통합 테스트로 작성해봐야겠다.
'프로젝트 > 크루트' 카테고리의 다른 글
jpa 쿼리 수 줄이기 (0) | 2022.06.21 |
---|---|
ec2를 종료해서 도커와 젠킨스가 죽었을 때 살리는 법 (0) | 2022.05.02 |
service layer에서는 도대체 뭘 테스트 해야해요? (1) | 2022.03.31 |
Entity에 @Transactional을 쓰고 싶은데 그래도 될까? (0) | 2022.03.28 |
모바일 브라우저에서는 로그인이 안 되는 이유 (0) | 2022.03.25 |