테스트하고자 하고자 하는 Repository 코드는 다음과 같다.
BoardRepositoryImpl
public class BoardRepositoryImpl implements BoardRepositoryCustom {
private final JPAQueryFactory queryFactory;
public BoardRepositoryImpl(EntityManager em) {
this.queryFactory = new JPAQueryFactory(em);
}
@Override
public Page<RetrievePostListDto> findPagePostList(RetrievePostListCondition retrievePostListCondition, Pageable pageable) {
JPAQuery<RetrievePostListDto> query = queryFactory
.select(Projections.constructor(RetrievePostListDto.class,
board.boardTitle,
account.nickname,
board.createdDate,
board.boardContent,
board.views,
board.commentCnt,
board.likyCnt,
board.hasImage,
board.isNotice))
.from(board)
.join(board.account, account)
.where(categoryEq(retrievePostListCondition.getCategory()),
petTypeEq(retrievePostListCondition.getPetType()),
searchWordLike(retrievePostListCondition.getSearchWord()))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.orderBy(board.isNotice.desc());
for (Sort.Order o : pageable.getSort()) {
PathBuilder pathBuilder = new PathBuilder(
board.getType(), board.getMetadata()
);
query.orderBy(new OrderSpecifier(o.isAscending() ? Order.ASC : Order.DESC,
pathBuilder.get(o.getProperty())));
}
List<RetrievePostListDto> content = query.fetch();
JPAQuery<Long> countQuery = queryFactory
.select(board.count())
.from(board)
.where(categoryEq(retrievePostListCondition.getCategory()),
petTypeEq(retrievePostListCondition.getPetType()));
return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne);
}
private BooleanExpression categoryEq(String category) {
return category.equals("ALL") ? null : board.category.eq(BoardEnum.valueOf(category));
}
private BooleanExpression petTypeEq(String petType) {
return petType.equals("ALL") ? null : board.petType.eq(PetTypeEnum.valueOf(petType));
}
private BooleanExpression searchWordLike(String searchWord) {
return searchWord != null ? board.boardTitle.like("%" + searchWord + "%") : null;
}
}
- 이 코드는 Spring Data JPA를 사용해 Board 엔티티와 상호작용하는 커스텀 리포지토리 인터페이스의 구현 클래스이다.
- 이 클래스는 페이징 된 게시글 목록을 검색하기 위한 메서드를 구현한다.
- pageable.getSort()를 사용해 정렬 속성을 검색하고, OrderSpecifier를 사용해 쿼리에 정렬을 추가한다.
- 기본적으로 Board 객체의 공지글(isNotice) 필드를 확인해 '공지글(true)'인 경우 우선 조회되도록 한다.
- 그리고 Request Dto로 받은 정렬 필드로 정렬한다.
@DataJpaTest
@DataJpaTest 어노테이션은 JPA 관련 테스트를 작성할 때 사용되며, 일반적으로 테스트 시 필요한 빈들을 자동으로 등록해준다.
@DataJpaTest는 다음 작업을 수행한다.
- EntityManager를 포함한 JPA 관련 빈들을 자동으로 등록한다.
- 데이터베이스 테스트용 EmbeddedDatabase 또는 실제 데이터베이스를 사용한다.
- 기본적으로 @Transactional 어노텡시녀을 사용해, 테스트가 끝나면 롤백한다.
따라서 @DataJpaTest를 사용하면 JPA 관련 빈들을 직접 등록하거나 데이터베이스 설정을 따로 하지 않아도 JPA 관련 테스트를 쉽게 작성할 수 있다.
@DataJpaTest 더미데이터 생성
BoardRepositoryImplTest
@ActiveProfiles("test")
@DataJpaTest
class BoardRepositoryImplTest extends DummyObject {
@BeforeEach
public void setUp() {
autoIncrementReset();
dataSetting();
em.flush();
em.clear();
}
private void dataSetting() {
Account jjanggu = accountRepository.save(newAccount("kakao_1234", "짱구"));
Account yuli = accountRepository.save(newAccount("kakao_5678", "유리"));
Account cheolsu = accountRepository.save(newAccount("kakao_9101", "철수"));
Account maenggu = accountRepository.save(newAccount("kakao_9999", "맹구"));
Board jjangguBoard = boardRepository.save(newBoard(jjanggu, "jjangguPost", "jjangguContent", BoardEnum.FREE,
PetTypeEnum.DOG, false));
Board yuliBoard = boardRepository.save(newBoard(yuli, "yuliPost", "yuliContent", BoardEnum.FREE,
PetTypeEnum.RABBIT, false));
Board cheolsuBoard = boardRepository.save(newBoard(cheolsu, "cheolsuPost", "cheolsuContent", BoardEnum.MEDICAL,
PetTypeEnum.CAT, true));
Board maengguBoard = boardRepository.save(newBoard(maenggu, "maengguPost", "maengguContent", BoardEnum.MEDICAL,
PetTypeEnum.CAT, true));
likyRepository.save(newLiky(jjangguBoard, yuli.getId(), boardRepository));
likyRepository.save(newLiky(jjangguBoard, cheolsu.getId(), boardRepository));
likyRepository.save(newLiky(jjangguBoard, 100L, boardRepository));
likyRepository.save(newLiky(jjangguBoard, 101L, boardRepository));
likyRepository.save(newLiky(yuliBoard, jjanggu.getId(), boardRepository));
likyRepository.save(newLiky(yuliBoard, cheolsu.getId(), boardRepository));
likyRepository.save(newLiky(cheolsuBoard, jjanggu.getId(), boardRepository));
likyRepository.save(newLiky(cheolsuBoard, yuli.getId(), boardRepository));
likyRepository.save(newLiky(cheolsuBoard, 100L, boardRepository));
likyRepository.save(newLiky(maengguBoard, jjanggu.getId(), boardRepository));
likyRepository.save(newLiky(maengguBoard, yuli.getId(), boardRepository));
commentRepository.save(newComment(jjangguBoard, "짱구야 소꿉놀이 하자.", yuli.getId(), boardRepository));
commentRepository.save(newComment(jjangguBoard, "싫어 나 액션가면 봐야해.", jjanggu.getId(), boardRepository));
commentRepository.save(newComment(jjangguBoard, "바보", yuli.getId(), boardRepository));
commentRepository.save(newComment(jjangguBoard, "메롱", jjanggu.getId(), boardRepository));
commentRepository.save(newComment(jjangguBoard, "짱구야 공부하자.", cheolsu.getId(), boardRepository));
commentRepository.save(newComment(jjangguBoard, "부리부리 부리부리", jjanggu.getId(), boardRepository));
commentRepository.save(newComment(yuliBoard, "유리야 그거 재밌어?", jjanggu.getId(), boardRepository));
commentRepository.save(newComment(yuliBoard, "응 다음에 짱구도 같이 하자.", yuli.getId(), boardRepository));
commentRepository.save(newComment(yuliBoard, "내일보자 유리야", cheolsu.getId(), boardRepository));
commentRepository.save(newComment(yuliBoard, "그래", yuli.getId(), boardRepository));
commentRepository.save(newComment(cheolsuBoard, "철수야 소꿉놀이 하자.", yuli.getId(), boardRepository));
commentRepository.save(newComment(cheolsuBoard, "안돼 나 학원 가야 할 시간이야.", cheolsu.getId(), boardRepository));
commentRepository.save(newComment(cheolsuBoard, "철수야 사랑해", jjanggu.getId(), boardRepository));
commentRepository.save(newComment(cheolsuBoard, "으~ 징그러", cheolsu.getId(), boardRepository));
commentRepository.save(newComment(cheolsuBoard, "히히 철수도 좋으면서", jjanggu.getId(), boardRepository));
}
private void autoIncrementReset() {
em.createNativeQuery("ALTER TABLE account ALTER COLUMN account_id RESTART WITH 1").executeUpdate();
em.createNativeQuery("ALTER TABLE board ALTER COLUMN board_id RESTART WITH 1").executeUpdate();
em.createNativeQuery("ALTER TABLE liky ALTER COLUMN liky_id RESTART WITH 1").executeUpdate();
em.createNativeQuery("ALTER TABLE comment ALTER COLUMN comment_id RESTART WITH 1").executeUpdate();
}
}
- JUnit5의 어노테이션인 @BeforeEach를 사용해, 각각의 테스트 메서드 실행 전 setUp() 메서드를 실행한다.
- setUp() 메서드에서는 데이터베이스에 데이터를 추기화한다.
- 이를 위해 Account, Board, Liky, Comment 엔티티를 생성하고, 해당 엔티티를 데이터베이스에 저장한다.
- autoIncrementReset() 메서드를 호출해, 모든 테이블의 Auto Increment 값을 1로 리턴한다.
- @DataJpaTest에서는 @Sql("classpath:db/teardown.sql")을 사용해도 Auto_Increment가 초기화되지 않는다.
주의!!
setUp() 메서드 내에서 em.clear() 함수 호출 전에 em.flush() 호출을 수행하지 않으면 Board 객체의 views, likyCnt, commentCnt 값이 증가되어 데이터베이스에 저장되지 않아 테스트가 정상적으로 수행되지 않는다.
DummyObject
public class DummyObject {
protected Account newAccount(String username, String nickname) {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encPassword = passwordEncoder.encode("1234");
return Account.builder()
.username(username)
.password(encPassword)
.email(username + "@naver.com")
.nickname(nickname)
.role(AccountEnum.CUSTOMER)
.build();
}
protected Board newBoard(Account account, String title, String content,
BoardEnum category, PetTypeEnum petType, boolean isNotice) {
return Board.builder()
.account(account)
.boardTitle(title)
.boardContent(content)
.category(category)
.petType(petType)
.views(0L)
.likyCnt(0L)
.commentCnt(0L)
.hasImage(false)
.isNotice(isNotice)
.build();
}
protected Liky newLiky(Board board, Long accountId, BoardRepository boardRepository) {
board.increaseViews();
board.increaseLikyCnt();
if (boardRepository != null) {
boardRepository.save(board);
}
return new Liky(board, accountId);
}
protected Comment newComment(Board board, String commentContent,
Long accountId, BoardRepository boardRepository) {
board.increaseViews();
board.increaseCommentCnt();
if (boardRepository != null) {
boardRepository.save(board);
}
return new Comment(board, commentContent, accountId);
}
}
- 이 클래스는 테스트에서 사용될 여러 도메인 객체들을 생성하는 데 사용된다.
문제점
BoardRepositoryImplTest에서 DummyObject의 newLiky(), newComment() 메서드를 호출하면 파라미터로 전달된 Board 객체의 views, likyCnt, commentCnt 값이 증가되어 BoardRepository에 합병(Merge)되어 저장되어야 한다.
하지만 BoardRepositoryImplTest 클래스의 setUp() 메서드에서 em.clear()를 호출해 데이터베이스에 저장되지 않고 결국 Board 객체의 필드값이 증가되지 않는다.
이러한 이유로 em.clear() 메서드 호출 전 em.flush() 메서드를 호출해 데이터베이스에 증가된 필드값을 저장한다.
동적쿼리 테스트
BoarrdRepositoryImplTest
@ActiveProfiles("test")
@DataJpaTest
class BoardRepositoryImplTest extends DummyObject {
@Autowired
private AccountRepository accountRepository;
@Autowired
private BoardRepository boardRepository;
@Autowired
private CommentRepository commentRepository;
@Autowired
private LikyRepository likyRepository;
@Autowired
private EntityManager em;
@BeforeEach
public void setUp() {
autoIncrementReset();
dataSetting();
em.flush();
em.clear();
}
@Test
public void findPagePostList_all_createdDate_desc_test() {
// given
BoardReqDto.RetrievePostListCondition retrievePostListCondition =
new BoardReqDto.RetrievePostListCondition("ALL", "ALL", null);
PageRequest pageRequest = PageRequest.of(0, 4, Sort.by(Sort.Direction.DESC, "createdDate"));
// when
List<RetrievePostListDto> content =
boardRepository.findPagePostList(retrievePostListCondition, pageRequest)
.getContent();
// then
assertThat(content.get(0).getWriterNick()).isEqualTo("맹구");
assertThat(content.get(1).getWriterNick()).isEqualTo("철수");
assertThat(content.get(2).getWriterNick()).isEqualTo("유리");
assertThat(content.get(3).getWriterNick()).isEqualTo("짱구");
}
@Test
public void findPagePostList_all_likyCnt_desc_test() {
// given
BoardReqDto.RetrievePostListCondition retrievePostListCondition =
new BoardReqDto.RetrievePostListCondition("ALL", "ALL", null);
PageRequest pageRequest = PageRequest.of(0, 4, Sort.by(Sort.Direction.DESC, "likyCnt"));
// when
List<RetrievePostListDto> content =
boardRepository.findPagePostList(retrievePostListCondition, pageRequest)
.getContent();
// then
assertThat(content.get(0).getWriterNick()).isEqualTo("철수");
assertThat(content.get(1).getWriterNick()).isEqualTo("맹구");
assertThat(content.get(2).getWriterNick()).isEqualTo("짱구");
assertThat(content.get(3).getWriterNick()).isEqualTo("유리");
}
@Test
public void findPagePostList_all_views_desc_test() {
// given
BoardReqDto.RetrievePostListCondition retrievePostListCondition =
new BoardReqDto.RetrievePostListCondition("ALL", "ALL", null);
PageRequest pageRequest = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "views"));
// when
List<RetrievePostListDto> content =
boardRepository.findPagePostList(retrievePostListCondition, pageRequest)
.getContent();
content.forEach(b -> {
System.out.println("b.getTitle() = " + b.getTitle());
System.out.println("b.getViews() = " + b.getViews());
});
// then
assertThat(content.size()).isEqualTo(3);
assertThat(content.get(0).getWriterNick()).isEqualTo("철수");
assertThat(content.get(1).getWriterNick()).isEqualTo("맹구");
assertThat(content.get(2).getWriterNick()).isEqualTo("짱구");
}
@Test
public void findPagePostList_all_views_asc_test() {
// given
BoardReqDto.RetrievePostListCondition retrievePostListCondition =
new BoardReqDto.RetrievePostListCondition("ALL", "ALL", null);
PageRequest pageRequest = PageRequest.of(0, 4, Sort.by(Sort.Direction.ASC, "views"));
// when
List<RetrievePostListDto> content =
boardRepository.findPagePostList(retrievePostListCondition, pageRequest)
.getContent();
// then
assertThat(content.get(0).getWriterNick()).isEqualTo("맹구");
assertThat(content.get(1).getWriterNick()).isEqualTo("철수");
assertThat(content.get(2).getWriterNick()).isEqualTo("유리");
assertThat(content.get(3).getWriterNick()).isEqualTo("짱구");
}
@Test
public void findPagePostList_all_commentCnt_desc_test() {
// given
BoardReqDto.RetrievePostListCondition retrievePostListCondition =
new BoardReqDto.RetrievePostListCondition("ALL", "ALL", null);
PageRequest pageRequest = PageRequest.of(0, 4, Sort.by(Sort.Direction.DESC, "commentCnt"));
// when
List<RetrievePostListDto> content =
boardRepository.findPagePostList(retrievePostListCondition, pageRequest)
.getContent();
// then
assertThat(content.get(0).getWriterNick()).isEqualTo("철수");
assertThat(content.get(1).getWriterNick()).isEqualTo("맹구");
assertThat(content.get(2).getWriterNick()).isEqualTo("짱구");
assertThat(content.get(3).getWriterNick()).isEqualTo("유리");
}
@Test
public void findPagePostList_medical_commentCnt_desc_test() {
// given
BoardReqDto.RetrievePostListCondition retrievePostListCondition =
new BoardReqDto.RetrievePostListCondition("MEDICAL", "ALL", null);
PageRequest pageRequest = PageRequest.of(0, 4, Sort.by(Sort.Direction.DESC, "commentCnt"));
// when
List<RetrievePostListDto> content =
boardRepository.findPagePostList(retrievePostListCondition, pageRequest)
.getContent();
// then
assertThat(content.size()).isEqualTo(2);
assertThat(content.get(0).getWriterNick()).isEqualTo("철수");
assertThat(content.get(1).getWriterNick()).isEqualTo("맹구");
}
@Test
public void findPagePostList_cat_commentCnt_desc_test() {
// given
BoardReqDto.RetrievePostListCondition retrievePostListCondition =
new BoardReqDto.RetrievePostListCondition("ALL", "CAT", null);
PageRequest pageRequest = PageRequest.of(0, 4, Sort.by(Sort.Direction.DESC, "commentCnt"));
// when
List<RetrievePostListDto> content =
boardRepository.findPagePostList(retrievePostListCondition, pageRequest)
.getContent();
// then
assertThat(content.size()).isEqualTo(2);
assertThat(content.get(0).getWriterNick()).isEqualTo("철수");
assertThat(content.get(1).getWriterNick()).isEqualTo("맹구");
}
@Test
public void findPagePostList_medical_cat_commentCnt_desc_test() {
// given
BoardReqDto.RetrievePostListCondition retrievePostListCondition =
new BoardReqDto.RetrievePostListCondition("MEDICAL", "CAT", null);
PageRequest pageRequest = PageRequest.of(0, 4, Sort.by(Sort.Direction.DESC, "commentCnt"));
// when
List<RetrievePostListDto> content =
boardRepository.findPagePostList(retrievePostListCondition, pageRequest)
.getContent();
// then
assertThat(content.size()).isEqualTo(2);
assertThat(content.get(0).getWriterNick()).isEqualTo("철수");
assertThat(content.get(1).getWriterNick()).isEqualTo("맹구");
}
private void dataSetting() {
Account jjanggu = accountRepository.save(newAccount("kakao_1234", "짱구"));
Account yuli = accountRepository.save(newAccount("kakao_5678", "유리"));
Account cheolsu = accountRepository.save(newAccount("kakao_9101", "철수"));
Account maenggu = accountRepository.save(newAccount("kakao_9999", "맹구"));
Board jjangguBoard = boardRepository.save(newBoard(jjanggu, "jjangguPost", "jjangguContent", BoardEnum.FREE,
PetTypeEnum.DOG, false));
Board yuliBoard = boardRepository.save(newBoard(yuli, "yuliPost", "yuliContent", BoardEnum.FREE,
PetTypeEnum.RABBIT, false));
Board cheolsuBoard = boardRepository.save(newBoard(cheolsu, "cheolsuPost", "cheolsuContent", BoardEnum.MEDICAL,
PetTypeEnum.CAT, true));
Board maengguBoard = boardRepository.save(newBoard(maenggu, "maengguPost", "maengguContent", BoardEnum.MEDICAL,
PetTypeEnum.CAT, true));
likyRepository.save(newLiky(jjangguBoard, yuli.getId(), boardRepository));
likyRepository.save(newLiky(jjangguBoard, cheolsu.getId(), boardRepository));
likyRepository.save(newLiky(jjangguBoard, 100L, boardRepository));
likyRepository.save(newLiky(jjangguBoard, 101L, boardRepository));
likyRepository.save(newLiky(yuliBoard, jjanggu.getId(), boardRepository));
likyRepository.save(newLiky(yuliBoard, cheolsu.getId(), boardRepository));
likyRepository.save(newLiky(cheolsuBoard, jjanggu.getId(), boardRepository));
likyRepository.save(newLiky(cheolsuBoard, yuli.getId(), boardRepository));
likyRepository.save(newLiky(cheolsuBoard, 100L, boardRepository));
likyRepository.save(newLiky(maengguBoard, jjanggu.getId(), boardRepository));
likyRepository.save(newLiky(maengguBoard, yuli.getId(), boardRepository));
commentRepository.save(newComment(jjangguBoard, "짱구야 소꿉놀이 하자.", yuli.getId(), boardRepository));
commentRepository.save(newComment(jjangguBoard, "싫어 나 액션가면 봐야해.", jjanggu.getId(), boardRepository));
commentRepository.save(newComment(jjangguBoard, "바보", yuli.getId(), boardRepository));
commentRepository.save(newComment(jjangguBoard, "메롱", jjanggu.getId(), boardRepository));
commentRepository.save(newComment(jjangguBoard, "짱구야 공부하자.", cheolsu.getId(), boardRepository));
commentRepository.save(newComment(jjangguBoard, "부리부리 부리부리", jjanggu.getId(), boardRepository));
commentRepository.save(newComment(yuliBoard, "유리야 그거 재밌어?", jjanggu.getId(), boardRepository));
commentRepository.save(newComment(yuliBoard, "응 다음에 짱구도 같이 하자.", yuli.getId(), boardRepository));
commentRepository.save(newComment(yuliBoard, "내일보자 유리야", cheolsu.getId(), boardRepository));
commentRepository.save(newComment(yuliBoard, "그래", yuli.getId(), boardRepository));
commentRepository.save(newComment(cheolsuBoard, "철수야 소꿉놀이 하자.", yuli.getId(), boardRepository));
commentRepository.save(newComment(cheolsuBoard, "안돼 나 학원 가야 할 시간이야.", cheolsu.getId(), boardRepository));
commentRepository.save(newComment(cheolsuBoard, "철수야 사랑해", jjanggu.getId(), boardRepository));
commentRepository.save(newComment(cheolsuBoard, "으~ 징그러", cheolsu.getId(), boardRepository));
commentRepository.save(newComment(cheolsuBoard, "히히 철수도 좋으면서", jjanggu.getId(), boardRepository));
}
private void autoIncrementReset() {
em.createNativeQuery("ALTER TABLE account ALTER COLUMN account_id RESTART WITH 1").executeUpdate();
em.createNativeQuery("ALTER TABLE board ALTER COLUMN board_id RESTART WITH 1").executeUpdate();
em.createNativeQuery("ALTER TABLE liky ALTER COLUMN liky_id RESTART WITH 1").executeUpdate();
em.createNativeQuery("ALTER TABLE comment ALTER COLUMN comment_id RESTART WITH 1").executeUpdate();
}
}
'기술 블로그 > MOLLY' 카테고리의 다른 글
@PatchMapping validation 에러 (0) | 2023.04.30 |
---|---|
사용자 요청 시 권한 처리에 관하여 (0) | 2023.04.09 |
스프링 부트 + 리액트 + JWT + 스프링 시큐리티 + OAUTH2 소셜 로그인 기능 구현 (3) | 2023.04.08 |