본문 바로가기
Spring/SpringBoot

[SpringBoot] Layer별 테스트 코드 작성하기 (1) - JPA를 이용한 Repository 테스트

by J4J 2024. 2. 3.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 Layer별 테스트 코드 작성하는 방법 첫 번째인 JPA를 이용한 Repository 테스트하는 방법에 대해 적어보는 시간을 가져보려고 합니다.

 

 

 

JpaRepository 테스트

 

JPA를 이용하는 환경에서 repository 테스트를 수행할 때 가장 대표적으로 해볼 수 있는 테스트는 JpaRepository 사용에 대한 테스트입니다.

 

JpaRepository를 테스트하기 위해서는 jpa에서 제공해 주는 @DataJpaTest 어노테이션을 활용하면 되는데 해당 어노테이션 내부를 확인해 보면 다음과 같이 다양한 설정 값들이 들어 있는 것을 볼 수 있습니다.

 

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(DataJpaTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
@OverrideAutoConfiguration(enabled = false)
@TypeExcludeFilters(DataJpaTypeExcludeFilter.class)
@Transactional
@AutoConfigureCache
@AutoConfigureDataJpa
@AutoConfigureTestDatabase
@AutoConfigureTestEntityManager
@ImportAutoConfiguration
public @interface DataJpaTest {

   ...
   
}

 

 

반응형

 

 

@AutoConfigureDataJpa와 같이 jpa를 사용하여 테스트할 수 있는 설정 값들을 뿐만 아니라 @Transactional과 같이 테스트에 사용된 데이터들이 실제 DB에는 적용되지 않도록 도와주는 설정도 포함되어 있습니다.

 

또한 repository 관련 테스트에 필요한 설정들만 들어있기 때문에 repository 테스트를 수행할 때 repository 동작과 관련 없는 controller, service 등을 포함한 모든 설정을 하지 않습니다.

 

그러다 보니 repostiory 테스트를 수행할 때 테스트를 무겁게 만드는 spring 동작을 위한 모든 설정을 하지 않아 더 빠른 속도로 테스트를 수행할 수 있게 도와줍니다.

 

이처럼 해당 어노테이션에 테스트를 위해 사용할 수 있는 다양한 것들이 담겨있기 때문에 부가적인 설정 없이도 JpaRepository 테스트를 수행할 수 있도록 도와줍니다.

 

 

 

 

이제는 테스트 코드를 구성해 보겠습니다.

 

간단하게 JpaRepository에서 제공해 주는 findById 메서드 테스트를 한다고 가정하면 다음과 같이 코드가 작성될 수 있습니다.

 

[ 1. Entity 확인 ]

 

package com.jforj.repositorytest.entity;

import jakarta.persistence.*;
import lombok.*;

@ToString
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "student")
public class StudentEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long no;
    private String name;
    private int age;
    private long schoolNo;

    @Builder
    public StudentEntity(String name, int age, long schoolNo) {
        this.name = name;
        this.age = age;
        this.schoolNo = schoolNo;
    }
}

 

 

 

[ 2. JpaRepository 확인 ]

 

package com.jforj.repositorytest.repository;

import com.jforj.repositorytest.entity.StudentEntity;
import org.springframework.data.jpa.repository.JpaRepository;

public interface StudentJpaRepository extends JpaRepository<StudentEntity, Long> {

}

 

 

 

 

[ 3. 테스트 코드 작성 ]

 

package com.jforj.repositorytest.repository;

import com.jforj.repositorytest.entity.StudentEntity;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;

import static org.junit.jupiter.api.Assertions.assertNotNull;

// JPA 테스트 코드 구성 설정을 도와주는 어노테이션
@DataJpaTest
// DataJpaTest에 의해 테스트용 데이터베이스가 자동으로 구성되지만, 실제 사용되는 DB를 사용하고 싶은 경우 다음과 같이 설정
// 실제 사용되는 DB를 사용하지 않는 경우 테스트용 데이터베이스 구성을 위한 추가 설정 필요
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class StudentRepositoryTest {

    @Autowired
    private StudentJpaRepository studentJpaRepository;

    @Test
    void findByIdTest() {
        // given
        StudentEntity savedStudentEntity =
                studentJpaRepository
                        .save(
                                StudentEntity
                                        .builder()
                                        .name("givenName")
                                        .age(20)
                                        .schoolNo(1L)
                                        .build()
                        );

        // when
        StudentEntity foundStudentEntity =
                studentJpaRepository
                        .findById(savedStudentEntity.getNo())
                        .get();

        // then
        assertNotNull(foundStudentEntity);
    }
}

 

 

 

[ 4. 테스트 결과 확인 ]

 

JpaRepository 테스트 결과 확인

 

 

 

 

@Repository 테스트

 

이번에는 JpaRepository가 아닌 @Repository 어노테이션을 이용하여 repository 클래스 파일을 자체적으로 구성했을 때를 얘기해 보겠습니다.

 

일반적으로 JPA를 사용하고 있는데 @Repository 어노테이션을 이용하여 repository 클래스 파일을 따로 구성하는 경우는 repository에서 발생될 수 있는 기능을 명확하게 메서드화 시킬 때 사용됩니다.

 

그리고 JpaRepostiory 같은 경우는 repository 클래스 내부에 의존성 주입되어 사용됩니다.

 

 

 

위에서 JPA 테스트를 위해 사용되고 있던 @DataJpaTest는 보시다시피 JpaRepository에서 테스트할 수 있는 기본 설정들을 제공해주고 있습니다.

 

하지만 지금처럼 JpaRepository를 바로 사용하지 않고 @Repository 어노테이션을 이용하여 repository 클래스 파일을 구성한 것에 대한 설정까지 기본적으로 지원해주지 않습니다.

 

그래서 @DataJpaTest 어노테이션에 @Repository 어노테이션이 설정된 곳까지 테스트를 수행할 수 있도록 추가적인 설정을 해줘야 합니다.

 

 

 

 

간단하게 모든 이름 정보를 조회하는 테스트 코드를 작성해 보겠습니다.

 

아래 설정들은 위의 설정을 기반으로 한 추가적인 변경점입니다.

 

[ 1. Repository 클래스 추가 ]

 

package com.jforj.repositorytest.repository;

import com.jforj.repositorytest.entity.StudentEntity;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.stream.Collectors;

@Repository
@RequiredArgsConstructor
public class StudentRepository {
    private final StudentJpaRepository studentJpaRepository;

    public List<String> selectNames() {
        List<StudentEntity> studentEntities = studentJpaRepository.findAll();
        return studentEntities
                .stream()
                .map(StudentEntity::getName)
                .collect(Collectors.toList());
    }
}

 

 

 

 

[ 2. 테스트 코드 변경 ]

 

package com.jforj.repositorytest.repository;

import com.jforj.repositorytest.entity.StudentEntity;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Repository;

import static org.junit.jupiter.api.Assertions.assertNotNull;

// JPA 테스트 코드 구성 설정을 도와주는 어노테이션 ( + @Repository 관련 설정 포함)
@DataJpaTest(includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Repository.class))
// DataJpaTest에 의해 테스트용 데이터베이스가 자동으로 구성되지만, 실제 사용되는 DB를 사용하고 싶은 경우 다음과 같이 설정
// 실제 사용되는 DB를 사용하지 않는 경우 테스트용 데이터베이스 구성을 위한 추가 설정 필요
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class StudentRepositoryTest {

    @Autowired
    private StudentJpaRepository studentJpaRepository;

    @Autowired
    private StudentRepository studentRepository;

    @Test
    void selectNamesTest() {
        // given
        String givenName = "givenName";
        studentJpaRepository.save(
                StudentEntity
                        .builder()
                        .name(givenName)
                        .age(20)
                        .schoolNo(1L)
                        .build()
        );

        // when
        String filterName =
                studentRepository
                        .selectNames()
                        .stream()
                        .filter(name -> name.equals(givenName))
                        .findFirst()
                        .get();

        // then
        assertNotNull(filterName);
    }
}

 

 

 

[ 3. 테스트 결과 확인 ]

 

@Repository 테스트 결과 확인

 

 

 

 

Querydsl 테스트

 

이번에는 querydsl을 사용하는 경우에 대한 테스트 방법에 대해 소개드리겠습니다.

 

querydsl도 기본적으로 @Repository 테스트와 같이 querydsl 사용을 위한 repository 클래스 파일을 생성합니다.

 

그래서 @Repository 테스트를 하기 위한 설정과 크게 차이가 없습니다.

 

 

 

하지만 @Repository 테스트에서 @DataJpaTest에 추가적인 설정을 한 것처럼 테스트 코드에서 querydsl 사용을 위한 설정이 더 필요합니다.

 

querydsl을 적용할 때 JPAQueryFactory 설정을 위한 클래스 파일을 추가하고는 하는데, 해당 설정 파일을 테스트 코드 용도로 활용하기 위해 추가적으로 만들어 주시면 됩니다.

 

 

 

 

querydsl 테스트하는 방법도 @Repository와 동일하게 모든 이름을 조회하는 테스트를 작성해 보겠습니다.

 

이 또한 위의 설정 값들을 기반으로 한 추가적인 변경점입니다.

 

[ 1. Repository 클래스 변경 ]

 

package com.jforj.repositorytest.repository;

import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

import java.util.List;

import static com.jforj.repositorytest.entity.QStudentEntity.studentEntity;

@Repository
@RequiredArgsConstructor
public class StudentRepository {
    private final JPAQueryFactory jpaQueryFactory;

    public List<String> selectNames() {
        return jpaQueryFactory
                .select(studentEntity.name)
                .from(studentEntity)
                .fetch();
    }
}

 

 

 

[ 2. test 폴더 하위에 Querydsl 설정 추가 ]

 

package com.jforj.repositorytest.config;

import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;

@TestConfiguration
public class QuerydslTestConfig {

    @PersistenceContext
    private EntityManager entityManager;

    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(entityManager);
    }
}

 

querydsl test config 구조

 

 

 

 

[ 3. 테스트 코드 변경 ]

 

package com.jforj.repositorytest.repository;

import com.jforj.repositorytest.config.QuerydslTestConfig;
import com.jforj.repositorytest.entity.StudentEntity;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Repository;

import static org.junit.jupiter.api.Assertions.assertNotNull;

// JPA 테스트 코드 구성 설정을 도와주는 어노테이션 ( + @Repository 관련 설정 포함)
@DataJpaTest(includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Repository.class))
// DataJpaTest에 의해 테스트용 데이터베이스가 자동으로 구성되지만, 실제 사용되는 DB를 사용하고 싶은 경우 다음과 같이 설정
// 실제 사용되는 DB를 사용하지 않는 경우 테스트용 데이터베이스 구성을 위한 추가 설정 필요
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
// querydsl 사용을 위한 설정 파일 import
@Import(QuerydslTestConfig.class)
class StudentRepositoryTest {

    @Autowired
    private StudentJpaRepository studentJpaRepository;

    @Autowired
    private StudentRepository studentRepository;

    @Test
    void selectNamesTest() {
        // given
        String givenName = "givenName";
        studentJpaRepository.save(
                StudentEntity
                        .builder()
                        .name(givenName)
                        .age(20)
                        .schoolNo(1L)
                        .build()
        );

        // when
        String filterName =
                studentRepository
                        .selectNames()
                        .stream()
                        .filter(name -> name.equals(givenName))
                        .findFirst()
                        .get();

        // then
        assertNotNull(filterName);
    }
}

 

 

 

[ 4. 테스트 결과 확인 ]

 

Querydsl 테스트 결과 확인

 

 

 

 

Repository 테스트를 위한 커스텀 어노테이션

 

지금까지 작성된 내용들을 확인해 보면 repository와 관련된 다양한 테스트 코드를 모두 작성할 수 있습니다.

 

하지만 개발되는 프로젝트에 사용되는 repository의 개수가 많아질수록 느껴지는 불편함이 생길 수 있습니다.

 

 

 

 

현재 작성되어 있는 테스트 코드를 살펴보면 @DataJpaTest와 같이 repository 테스트를 위한 설정 값만 담긴 어노테이션이 사용되고 있지만 그 외 설정들이 담기기 때문에 다음과 같이 여러 개의 어노테이션이 작성된 것을 볼 수 있습니다.

 

// JPA 테스트 코드 구성 설정을 도와주는 어노테이션 ( + @Repository 관련 설정 포함)
@DataJpaTest(includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Repository.class))
// DataJpaTest에 의해 테스트용 데이터베이스가 자동으로 구성되지만, 실제 사용되는 DB를 사용하고 싶은 경우 다음과 같이 설정
// 실제 사용되는 DB를 사용하지 않는 경우 테스트용 데이터베이스 구성을 위한 추가 설정 필요
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
// querydsl 사용을 위한 설정 파일 import
@Import(QuerydslTestConfig.class)
class StudentRepositoryTest {

   ...
   
}

 

 

 

그리고 이런 설정은 프로젝트 내부에서 모두 사용되기 때문에 모든 repository 테스트를 작성하는 클래스 파일에 포함되어 있어야 합니다.

 

이처럼 매번 설정하는 것은 불편함을 발생시킬 수 있고 이를 해결하기 위해 우리 만의 repository 테스트 설정을 도와주는 커스텀 어노테이션을 만들어 볼 수 있습니다.

 

 

 

 

커스텀 어노테이션은 다음과 같이 만들어서 사용해 볼 수 있고 더 편리하게 repository 테스트를 위한 환경 구성을 하는 결과를 볼 수 있습니다.

 

[ 1. test 폴더 하위에 커스텀 어노테이션 추가 ]

 

package com.jforj.repositorytest.annotation;


import com.jforj.repositorytest.config.QuerydslTestConfig;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Repository;

import java.lang.annotation.*;

// annotation이 class, interface, record 등의 선언에 사용될 수 있도록 설정
@Target(ElementType.TYPE)
// annotation이 runtime 단계에서 사용될 수 있도록 설정
@Retention(RetentionPolicy.RUNTIME)
// annotation이 javadoc 문서화에 표현될 수 있도록 설정
@Documented
// annotation이 부모 클래스에 선언되어 있을 때 자식 클래스에도 상속되도록 설정
@Inherited
// JPA 테스트 코드 구성 설정을 도와주는 어노테이션 ( + @Repository 관련 설정 포함)
@DataJpaTest(includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Repository.class))
// DataJpaTest에 의해 테스트용 데이터베이스가 자동으로 구성되지만, 실제 사용되는 DB를 사용하고 싶은 경우 다음과 같이 설정
// 실제 사용되는 DB를 사용하지 않는 경우 테스트용 데이터베이스 구성을 위한 추가 설정 필요
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
// querydsl 사용을 위한 설정 파일 import
@Import(QuerydslTestConfig.class)
public @interface RepositoryTest {
}

 

커스텀 어노테이션 구조

 

 

 

 

[ 2. 테스트 코드 변경 ]

 

package com.jforj.repositorytest.repository;

import com.jforj.repositorytest.annotation.RepositoryTest;
import com.jforj.repositorytest.entity.StudentEntity;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

import static org.junit.jupiter.api.Assertions.assertNotNull;

// 모든 설정을 커스텀 어노테이션으로 한 개로 대체
@RepositoryTest
class StudentRepositoryTest {

    @Autowired
    private StudentJpaRepository studentJpaRepository;

    @Autowired
    private StudentRepository studentRepository;

    @Test
    void selectNamesTest() {
        // given
        String givenName = "givenName";
        studentJpaRepository.save(
                StudentEntity
                        .builder()
                        .name(givenName)
                        .age(20)
                        .schoolNo(1L)
                        .build()
        );

        // when
        String filterName =
                studentRepository
                        .selectNames()
                        .stream()
                        .filter(name -> name.equals(givenName))
                        .findFirst()
                        .get();

        // then
        assertNotNull(filterName);
    }
}

 

 

 

[ 3. 테스트 결과 확인 ]

 

커스텀 어노테이션 테스트 결과 확인

 

 

 

 

 

 

 

 

이상으로 Layer별 테스트 코드 작성하는 방법 첫 번째인 JPA를 이용한 Repository 테스트하는 방법에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

댓글