Spring Boot 없이 xml로 환경 설정 할 때 JUnit Bean 주입 (이슈 내용 정리)
개요회사 코드에서 Spring Boot없는 순수 Spring으로 xml 환경 설정을 진행하고 있습니다. 이 때, 평소에 Spring Boot를 쓰던 상황에 편하게 적용하던 테스트 클래스 Component 적용을 Spring 환경에서 어떻게 진행하는지 삽질한 내용을 정리해두고자 합니다.  적용법적용법은 하단의 블로그를 참고했습니다.https://codevang.tistory.com/259 jUnit, Spring-Test 라이브러리 사용법[ jUnit ] 전체 프로젝트(특히 WAS)를 구동하지 않고 단위 코드 테스트를 할 수 있게 해주는 라이브러리 [ Spring-Test ] jUnit을 확장한 스프링의 테스트 라이브러리 스프링 MVC 프로젝트를 진행할 때 코codevang.tistory.com 테스트 폴..
2024.10.28
Spring Boot Repository Test 간단하게 실습
개요 Spring Boot를 사용하여 프로젝트 구현 때, DB와 관련된 테스트를 진행하기 위한 방법을 연습하고자 했습니다. 분명 테스트 진행 후 롤백시켜서 DB에 영향을 안끼치는 무언가가 있었다는 점을 알고 있었지만 기억이 나지 않아 다시 공부하고 또 찾을 때를 대비해 기록을 남겨 놓고자 합니다. @DataJPATest 분명 테스트 진행 후 롤백시켜서 DB에 영향을 안끼치는 무언가를 잊어먹었던 그 무언가는 @DataJPATest입니다. @DataJPATest는 우선 기본적으로 메모리 DB를 사용하도록 되어 있고 @Transactional이 걸려있기에 테스트 이후 롤백되어 DB에 영향을 안미치도록 되어있습니다. 또한 만약 실제 DB에서의 테스트를 원한다면 설정을 통해 실제 DB 환경에서의 테스트도 가능하다..
2023.10.17

목차

    개요

    회사 코드에서 Spring Boot없는 순수 Spring으로 xml 환경 설정을 진행하고 있습니다. 이 때, 평소에 Spring Boot를 쓰던 상황에 편하게 적용하던 테스트 클래스 Component 적용을 Spring 환경에서 어떻게 진행하는지 삽질한 내용을 정리해두고자 합니다.

     

     

    적용법

    적용법은 하단의 블로그를 참고했습니다.

    https://codevang.tistory.com/259

     

    jUnit, Spring-Test 라이브러리 사용법

    [ jUnit ] 전체 프로젝트(특히 WAS)를 구동하지 않고 단위 코드 테스트를 할 수 있게 해주는 라이브러리 [ Spring-Test ] jUnit을 확장한 스프링의 테스트 라이브러리 스프링 MVC 프로젝트를 진행할 때 코

    codevang.tistory.com

     

    테스트 폴더에 설정값을 매번 붙여넣기도 번거로울 것 같아 locations를 사용하고 file: 을 붙여주어 full-path로 입력해 실제 개발 상황에 사용 중인 설정값을 읽어오도록 하였습니다. 다만 저희는 각 상황에 따라 설정파일을 다르게 관리하고 있었기에 아래 블로그 예시와 같이 여러 파일 값을 사용하도록 하였습니다. 또한 매번 입력하는 것이 번거로울 것 같아 블로그에 소개해준 내용처럼 상위 클래스에 설정값을 넣고 상속받아 사용하는 방법을 이용하고자 합니다.

    @ContextConfiguration
    (locations = {"file:src/main/webapp/WEB-INF/spring/root-context.xml",
    		"file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})

     

    이 방법대로 그대로 잘되었으면 좋았겠지만 2 가지 오류를 만났습니다.

     

     

    Spring Bean 초기화 중 EL (Exprssion Language) 관련 종속성 누락

    제가 만들어둔 설정 Bean들 중 @Valid를 사용하기 위해 만든 MehodValidationConfig 클래스가 있었습니다.

    public class MethodValidationConfig {
    
    	@Bean
    	public MethodValidationPostProcessor methodValidationPostProcessor() {
    		return new MethodValidationPostProcessor();
    	}
    
    	@Bean
    	public LocalValidatorFactoryBean localValidatorFactoryBean() {
    		return new LocalValidatorFactoryBean();
    	}
    
    }

     

    테스트 코드 실행 시 methodValidationPostProcessor에 에러가 발생하였다고 로그가 떴었습니다.

    더보기

    Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'methodValidationPostProcessor' defined in com.duzon.lulu.dataprotection.config.MethodValidationConfig: Invocation of init method failed; nested exception is javax.validation.ValidationException: HV000183: Unable to initialize 'javax.el.ExpressionFactory'. Check that you have the EL dependencies on the classpath, or use ParameterMessageInterpolator instead

    에러 원인은 Spring에서 메서드 유효성 검증 (Bean Validation)을 수행할 때, javax.el.ExpressionFactory가 필요하지만 클래스패스에 EL 라이브러리가 없어 초기화에 실패한 것이라고 하여 아래와 같이 javax.el 라이브러리를 추가해 해결하였습니다.

    <dependency>
        <groupId>org.glassfish</groupId>
        <artifactId>javax.el</artifactId>
        <version>3.0.0</version>
    </dependency>

     

    javax.el의 역할은 아래과 같다고 합니다.

    javax.el.ExpressionFactory는 유효성 검증 어노테이션 내에서 사용되는 표현식을 평가하는 기능을 제공합니다. 예를 들어, @Size(min = "${minSize}", max = "${maxSize}")와 같은 유효성 표현식이 있는 경우, 이 값을 동적으로 평가하려면 javax.el 라이브러리가 필요합니다. methodValidationPostProcessor는 이러한 표현식을 처리하고 유효성을 동적으로 검증할 수 있도록 설정합니다.

    회사 내부 라이브러리 Bean 생성 오류

    현재 재직 중인 회사에서는 내부에서 공용 라이브러리를 사용하도록 되어 있습니다. 이 Bean들을 생성하는데 아래와 같은 오류가 발생했습니다.

    더보기

    Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name '********': Unsatisfied dependency expressed through field '*******'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name '*******': Unsatisfied dependency expressed through field '*****'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean found for dependency [javax.servlet.http.HttpServletRequest]: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

    회사 내부 코드에서는 HttpServletRequest를 Autowired해서 사용하는 경우가 꽤 있는데 이 부분 때문에 오류가 발생한 것으로 보입니다.

     

    HttpServletRequest Bean을 자동 주입하려고 하는데 HTTP 요청을 처리할 수 있는 웹 컨텍스트가 없어서 발생하는 문제입니다. 테스트 환경에서 웹 컨텍스트를 명시적으로 설정하면 해결되는 문제라고 합니다. 

    @ExtendWith(SpringExtension.class)
    @WebAppConfiguration
    public class YourTestClass {
        // Test methods here
    }

     

    각 어노테이션의 의미는 아래와 같다고 합니다.

    1. @ExtendWith (SpringExtension.class)

    • 역할: Spring의 테스트 컨텍스트를 JUnit 5에서 사용할 수 있게 해주는 확장 클래스입니다.
    • 설명: 이 어노테이션을 통해 Spring 컨텍스트의 기본 기능을 테스트에 통합할 수 있습니다. @Autowired와 같은 의존성 주입, 트랜잭션 관리, @Transactional 테스트, 애플리케이션 컨텍스트 로딩 등을 지원합니다.
    • 사용법: @ExtendWith(SpringExtension.class)는 기본적으로 모든 Spring 통합 테스트에 포함되어야 하며, Spring의 다양한 테스트 어노테이션과 함께 사용됩니다.

    2. @WebAppConfiguration

    • 역할: Spring MVC 테스트 시 WebApplicationContext (웹 애플리케이션 컨텍스트)를 로딩하여 웹 관련 빈들을 사용할 수 있도록 설정합니다.
    • 설명: Spring MVC 애플리케이션의 웹 환경을 구성하는 WebApplicationContext를 로드하도록 Spring 테스트 컨텍스트에 지시합니다. 이를 통해 MockMvc와 같은 MVC 테스트 기능을 사용하거나 HttpServletRequest 같은 웹 관련 Bean을 사용할 수 있게 됩니다.
    • 사용법: @WebAppConfiguration은 일반적으로 웹 애플리케이션 관련 Bean이 필요한 통합 테스트에 추가하며, Spring MVC 컨트롤러와 서비스 계층 간의 상호 작용을 테스트하는 데 유용합니다.

    'Spring > Testing' 카테고리의 다른 글

    Spring Boot Repository Test 간단하게 실습  (0) 2023.10.17

    목차

      개요

      Spring Boot를 사용하여 프로젝트 구현 때, DB와 관련된 테스트를 진행하기 위한 방법을 연습하고자 했습니다. 분명 테스트 진행 후 롤백시켜서 DB에 영향을 안끼치는 무언가가 있었다는 점을 알고 있었지만 기억이 나지 않아 다시 공부하고 또 찾을 때를 대비해 기록을 남겨 놓고자 합니다.

       

      @DataJPATest

      분명 테스트 진행 후 롤백시켜서 DB에 영향을 안끼치는 무언가를 잊어먹었던 그 무언가는 @DataJPATest입니다. @DataJPATest는 우선 기본적으로 메모리 DB를 사용하도록 되어 있고 @Transactional이 걸려있기에 테스트 이후 롤백되어 DB에 영향을 안미치도록 되어있습니다.

      또한 만약 실제 DB에서의 테스트를 원한다면 설정을 통해 실제 DB 환경에서의 테스트도 가능하다고 합니다.

       

      실습

      실습은 간단하게 Member 클래스를 생성한 후 Member 클래스를 생성하고 조회하는 과정을 테스트해보는 것으로 진행했습니다.

       

      우선 Member 클래스를 생성해 Entity를 만들었습니다.

      @Entity
      @Getter
      @NoArgsConstructor
      public class Member {
      
          @Id
          @GeneratedValue(strategy = GenerationType.IDENTITY)
          private Long id;
          @Column(nullable = false)
          private String name;
          @Column(nullable = false)
          private String email;
          @Column(nullable = false)
          private String password;
      
          @Builder
          public Member(String name, String email, String password) {
              this.name = name;
              this.email = email;
              this.password = password;
          }
      
      }

       

      이후 JpaRepository를 상속한 MemberRepository를 생성하여 CRUD가 가능하도록 만들었습니다.

      public interface MemberRepository extends JpaRepository<Member, Long> {
      }

       

      마지막으로 @DataJpaTest를 이용한 테스트를 생성한 후 저장 테스트와 조회 테스트를 진행했습니다.

      @DataJpaTest
      class MemberRepositoryTest {
      
          @Autowired
          private MemberRepository memberRepository;
      
          @Test
          @DisplayName("멤버 DB 저장 테스트")
          void saveMember() {
              Member member = Member.builder()
                      .email("taehee@tistory.com")
                      .name("taehee")
                      .password("1234")
                      .build();
      
              Member savedMember = memberRepository.save(member);
      
              assertThat(member).isSameAs(savedMember);
          }
      
          @Test
          @DisplayName("멤버 DB 조회 테스트")
          void findMember() {
              Member member = Member.builder()
                      .email("taehee@tistory.com")
                      .name("taehee")
                      .password("1234")
                      .build();
      
              Member savedMember = memberRepository.save(member);
      
              Member findMember = memberRepository.findById(member.getId()).orElseThrow(() -> new IllegalArgumentException("Member Not Found, memberId: " + savedMember.getId()));
      
              assertThat(findMember).isSameAs(savedMember);
          }
      
      }

       

      마치며

      정말 간단한 테스트였고 @DataJpaTest를 활용해본다 정도였습니다. 실제 서비스 구현할 때 발생하는 Join이라든가 복잡한 Query로 생성된 결과물을 테스트하는 방법을 실험해봐야 조금 더 감이 잡힐 것 같습니다. 또한 찾아보니 Embedded MongoDB Database라는 것도 있어서 MongoDB를 사용하는 유저에게 유닛 테스트를 위한 인메모리 MongoDB를 지원하는 것으로 보였습니다.

      그리고 Spring Testing Document를 보니 굉장히 뭐가 많던데 하나씩 공부를 해봐야 할 것 같습니다.

       

       

      Reference

      https://dublin-java.tistory.com/49

      https://jiminidaddy.github.io/dev/2021/05/20/dev-spring-%EB%8B%A8%EC%9C%84%ED%85%8C%EC%8A%A4%ED%8A%B8-Repository/

      https://cobbybb.tistory.com/23

      https://0soo.tistory.com/40#Entity%-C%--Repository

      https://docs.spring.io/spring-boot/docs/1.4.2.RELEASE/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-testing-autoconfigured-jpa-test

      https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTest.html