쌩로그

@DataJpaTest VS @SpringBootTest (feat.QueryDSL) 본문

TroubleShooting & 고민/BE

@DataJpaTest VS @SpringBootTest (feat.QueryDSL)

.쌩수. 2024. 10. 7. 22:08
반응형

목차

  1. 포스팅 개요
  2. 본론
  3. 요약 및 참고 블로그

1. 포스팅 개요

해당 포스팅은 트러블 슈팅 포스팅이다.

요즘 부트캠프 때 진행했던 메인 프로젝트르 리팩터링 하는 중이다.
최대한 테스트 코드를 많이 짜고 있다.

일단 내가 맡았던 도메인이었던 Borrow 라는 대여 CRUD를 지금 개발 & 구현 중인데,

목록 쪽 개발중이다.

여러 건의 게시물 목록을 응답하기 위해서 QueryDSL을 사용 중이다.
(나중에는 JOOQ 배우고 JOOQ로 변경해볼 예정이다.)

지금까지 트러블 슈팅 만난 배경이다.

어디서 문제를 만났는지는 본론에서 살펴보자.

2. 본론

두 개의 코드를 아래에 쓸 것이다.
근데 진짜 똑같다.

한 군데가 다르다.

근데 그 한 군데 때문에 테스트가 성공하거나, 테스트가 실패한다.
(import와 패키지는 생략한다.)

먼저 성공하는 코드다.

@SpringBootTest  
class BorrowQueryRepositoryImplTest {  

    @Autowired  
    EntityManager em;  

    @Autowired  
    BorrowRepository borrowRepository;  

    @Autowired  
    BorrowQueryRepository borrowQueryRepository;  

    @Test  
    void getBorrowListTest() {  
        //given  

        Borrow dddBorrow = Borrow.createBorrow(getCreateBorrowDto(1L, "DDD", "도메인 주도 개발 대여해드립니다."));  
        Borrow caBorrow = Borrow.createBorrow(getCreateBorrowDto(2L, "클린 아키텍처", "클린 아키텍처 빌려드립니다."));  
        Borrow rmBorrow = Borrow.createBorrow(getCreateBorrowDto(3L, "Real MySql 1", "Real MySql 1 빌려드립니다."));  

        borrowRepository.saveAll(List.of(dddBorrow, caBorrow, rmBorrow));  

        //when  
        List<BorrowListQueryDto> borrowList = borrowQueryRepository.getBorrowList(null, null);  

        //then  
        assertThat(borrowList).hasSize(3);  
        assertThat(borrowList).extracting("bookTitle")  
                .containsExactlyInAnyOrder("DDD", "클린 아키텍처", "Real MySql 1");  

    }  


    private CreateBorrowDto getCreateBorrowDto(Long memberId, String bookTitle, String title) {  
        return CreateBorrowDto.builder()  
                .title(title)  
                .content("책 빌려드립니다.")  
                .author("에릭 에반스")  
                .bookTitle(bookTitle)  
                .publisher("한빛? 에이콘이었나..?")  
                .thumbnail(null)  
                .memberId(memberId)  
                .build();  
    }  
}

테스트 성공~

다음은 실패하는 코드다.

@DataJpaTest   
class BorrowQueryRepositoryImplTest {  

    @Autowired  
    EntityManager em;  

    @Autowired  
    BorrowRepository borrowRepository;  

    @Autowired  
    BorrowQueryRepository borrowQueryRepository;  

    @AfterEach  
    void afterEach() {  
        borrowRepository.deleteAllInBatch();  
    }  

    @Test  
    void getBorrowListTest() {  
        //given  

        Borrow dddBorrow = Borrow.createBorrow(getCreateBorrowDto(1L, "DDD", "도메인 주도 개발 대여해드립니다."));  
        Borrow caBorrow = Borrow.createBorrow(getCreateBorrowDto(2L, "클린 아키텍처", "클린 아키텍처 빌려드립니다."));  
        Borrow rmBorrow = Borrow.createBorrow(getCreateBorrowDto(3L, "Real MySql 1", "Real MySql 1 빌려드립니다."));  

        borrowRepository.saveAll(List.of(dddBorrow, caBorrow, rmBorrow));  

        //when  
        List<BorrowListQueryDto> borrowList = borrowQueryRepository.getBorrowList(null, null);  

        //then  
        assertThat(borrowList).hasSize(3);  
        assertThat(borrowList).extracting("bookTitle")  
                .containsExactlyInAnyOrder("DDD", "클린 아키텍처", "Real MySql 1");  

    }  



    private CreateBorrowDto getCreateBorrowDto(Long memberId, String bookTitle, String title) {  
        return CreateBorrowDto.builder()  
                .title(title)  
                .content("책 빌려드립니다.")  
                .author("에릭 에반스")  
                .bookTitle(bookTitle)  
                .publisher("한빛? 에이콘이었나..?")  
                .thumbnail(null)  
                .memberId(memberId)  
                .build();  
    }  
}

테스트 실패~
로그를 보자.

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'refactoring.bookvillage.domain.borrow.repository.query.BorrowQueryRepositoryImplTest': Unsatisfied dependency expressed through field 'borrowQueryRepository': No qualifying bean of type 'refactoring.bookvillage.domain.borrow.repository.query.BorrowQueryRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

그니까 refactoring.bookvillage.domain.borrow.repository.query.BorrowQueryRepository 타입의 Bean을 찾지 못했다는 거다.

근데 분명히 나는 @Repository 애너테이션을 보란듯이 선언했다...

근데 왜 안 될까??

답은 저기 위에 작성한 코드의 클래스 레벨을 보면 된다.

포스팅 제목과 같이

@SpringBootTest를 쓰면 성공하고,
@DataJpaTest를 쓰면 실패한다.

처음엔 "타입이 있는데 왜 없다고 나오지?" 하면서 인터페이스-구현체 로 작성한 구조를 바꿔보기도 하는데 똑같은 에러가 나왔다.

설마 했는데, @DataJpaTest 문제였다.

@DataJpaTest@SpringBootTest 와는 달리 서버 실행시 필요한 모든 Bean 을 스프링 컨텍스트에 주입하는 것이 아니라, JPA에 특화된 Bean들만 주입해주고, 테스트하도록 해서 부하를 조큼 덜 일으킨다.(그래봐야 지금 규모상으로 얼마 차이 안 나지만...)

여하튼 QueryDSL을 사용하는 BorrowQueryRepository를 Jpa의 Bean으로 인식하지 않아서 생긴 문제였다.

이미 겪은 사람이 있었다.
그래서 그 블로그를 보고 @DataJpaTest 만으로도 해당 테스트가 돌아가게끔 하기 위해 한 번 바꿔보도록 하겠다.

결론적으로 말하면 Configuration 설정을 해주면 되더라.

참고 블로그는 아래에서도 기입할 거지만, 일단 [여기]([QueryDsl] @DataJpaTest 에서 @Repository 테스트하기 — 기억의 정류장 (tistory.com))다.

일단 나는 테스트 경로의 초기 테스트 파일이 있는 경로에 configuration 이라는 패키지를 만들고 그 안에서 Config 설정을 할 클래스를 작성할 것이다.

그리고 다음과 같이 TestQueryDslConfig 라는 클래스를 작성했다.

import jakarta.persistence.EntityManager;  
import jakarta.persistence.PersistenceContext;  
import org.springframework.boot.test.context.TestConfiguration;  
import org.springframework.context.annotation.Bean;  
import refactoring.bookvillage.domain.borrow.repository.query.BorrowQueryRepository;  
import refactoring.bookvillage.domain.borrow.repository.query.BorrowQueryRepositoryImpl;  

@TestConfiguration  
public class TestQueryDslConfig {  

    @PersistenceContext  
    private EntityManager em;  

    @Bean  
    public BorrowQueryRepository borrowQueryRepository() {  
        return new BorrowQueryRepositoryImpl(em);  
    }  
}

참고 블로그는 JPAQueryFactory 도 의존성을 주입받고 있어서
EntityManagerJPAQueryFactory
JPAQueryFactoryQueryDslRepository로 의존하는 구조지만,

내가 사용하는 BorrowQueryRepositoryImpl 클래스는 EntityManager를 주입받고 생성자 안에서 JPAQueryFactory 를 생성하고 있기 때문에 위와 같이 작성했다.

@Repository  
public class BorrowQueryRepositoryImpl implements BorrowQueryRepository {  

    private final JPAQueryFactory queryFactory;  

    public BorrowQueryRepositoryImpl(EntityManager em) {  
        this.queryFactory = new JPAQueryFactory(em);  
    }
    ...
}

그리고 테스트 코드에 다음과 같이 Config 클래스를 import 해주면 된다.

@Import(TestQueryDslConfig.class)  // Import  
@DataJpaTest  
class BorrowQueryRepositoryImplTest {  

    @Autowired  
    EntityManager em;  

    @Autowired  
    BorrowRepository borrowRepository;  

    @Autowired  
    BorrowQueryRepository borrowQueryRepository;  


    @Test  
    void getBorrowListTest() {  
        //given  

        Borrow dddBorrow = Borrow.createBorrow(getCreateBorrowDto(1L, "DDD", "도메인 주도 개발 대여해드립니다."));  
        Borrow caBorrow = Borrow.createBorrow(getCreateBorrowDto(2L, "클린 아키텍처", "클린 아키텍처 빌려드립니다."));  
        Borrow rmBorrow = Borrow.createBorrow(getCreateBorrowDto(3L, "Real MySql 1", "Real MySql 1 빌려드립니다."));  

        borrowRepository.saveAll(List.of(dddBorrow, caBorrow, rmBorrow));  

        //when  
        List<BorrowListQueryDto> borrowList = borrowQueryRepository.getBorrowList(null, null);  

        //then  
        assertThat(borrowList).hasSize(3);  
        assertThat(borrowList).extracting("bookTitle")  
                .containsExactlyInAnyOrder("DDD", "클린 아키텍처", "Real MySql 1");  

    }

성공~

눈이 편안하다~

3. 요약 및 참고 블로그

참고 블로그 : https://rachel0115.tistory.com/entry/QueryDsl-DataJpaTest-%EC%97%90%EC%84%9C-Repository-%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%95%98%EA%B8%B0

요약

QueryDsl 사용시 테스트 코드작성할 떄 @DataJpaTest 를 사용해서 안 되었는데,
Config를 설정하고 Import를 해주니깐 되었다.

728x90
Comments