프로젝트/크루트

jpa의 orphanRemoval이 잘 작동하는 지 테스트가 하고 싶을 때

발전생 2022. 4. 1. 11:09

테스트코드를 작성하지 않고 손수 인수테스트를 하며 개발하는 동안 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에 정상적으로 반영되는 지를 테스트하는 통합 테스트를 작성할 것을 권장하고 있다.

 

통합 테스트로 작성해봐야겠다.