티스토리 뷰

스프링 부트 REST API WEB 프로젝트

깃헙 링크

https://github.com/choiwoonsik/springboot_RestApi_App_Project/tree/main/restApiSpringBootApp

수행 목록

  1. 환경구성 및 helloworld 출력
  2. H2 DB 연동
  3. Swagger API 문서 연동
  4. REST API 설계
  5. RestControllerAdvice를 이용한 통합 예외 처리
  6. Entity - DTO 분리
  7. MessageSource를 이용해 예외 메시지 다국화
  8. JPA Aduting을 이용해 객체 생성시간/수정시간 적용
  9. 스프링 시큐리티 + Jwt를 이용해서 인증 및 권한 체크
  10. 스프링 시큐리티 AuthenticationEntryPoint, AccessDenied로 인증 및 인가 예외처리
  11. Jwt AccessToken + RefreshToken으로 보안성과 사용자 편의성 고도화하기
  12. JUnit Test (단위 테스트)
  13. JUnit Test (통합 테스트)
  14. OAuth 2.0 정리
  15. OAuth 2.0 카카오 로그인 part.1 Authorization code + Token 발급
  16. OAuth 2.0 카카오 로그인 part.2 토큰으로 회원 가입 / 로그인
  17. OAuth 2.0 카카오 로그인 테스트 검증
  18. 환경별 설정을 위해서profile 분리하기 

 


정상적인 구현을 확인하기 위한 JUnit 테스트를 진행해보자

의존성 추가

// 스프링 테스트
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'

 

테스트는 두 종류로 나눠볼 수 있다.

  1. 통합 테스트
    • @SpringBootTest
    • 전체적인 플로우를 검증하기 위한 테스트이다.
    • 애플리케이션의 설정, 모든 Bean을 전부 로드해서 테스트 하므로 운영환경과 가장 유사한 테스트가 가능하다.
    • 단점
      • 그만큼 많은 Bean을 로드하므로 시간도 오래걸리고 무겁다.
      • 테스트 단위가 크므로 디버깅이 어렵다. 어디가 잘못됐는지 찾기가 힘듬 
  2. 단위 테스트
    • @DataJpaTest
    • @WebMvcTest
    • @RestClientTest
    • @JsonTest
    • 단위 테스트는 각 단위 테스트 성격에 맞게 필요한 컴포넌트만 확인해서 Bean으로 등록한다.
    • 기능 검증 위주의 테스트이다.
    • 따라서 더욱 가볍고 빠른 테스트가 가능하다.
    • 단점
      • 전체적인 로직을 테스트가 힘들다.

 

단위 테스트에 대해서 알아보자

1. JPA 테스트를 위한 @DataJpaTest

현재 User Entity는 Spring Data JPA 를 상속받아서 구현중에 있다. @DataJpaTest 애노테이션을 사용하면 이러한 JPA를 손쉽게 테스트할 수 있는 환경을 만들어준다.

  • @DataJpaTest를 달아주면 다른 컴포넌트는 로드하지 않고 @Entity를 읽어서 스프링 데이터 JPA Repository내용을 테스트할 수 있는 환경을 만들어 준다.
  • @Transactional을 포함하고 있으므로 테스트가 완료되면 따로 롤백을 해주지 않아도 된다.
  • JPA 관련된 설정만 불러온다. (즉, Entity 관련 테스트를 할 때 용이)
  • 설정이 정상적인지, JPA를 사용해서 데이터를 올바르게 등록 / 수정 / 삭제 / 조회 하는지의 테스트가 가능하다
  • 기본적으로 in-memory embedded database에 대한 테스트를 진행한다
    • @AutoConfigureTestDatabase(reploace = AutoConfigureTestDatabase.Replace.NONE) 애노테이션을 달아주면 메모리 DB가 아니라 실제 DB에서 테스트가 가능하다.
  • @ActiveProfiles("test")등의 프로파일 설정도 가능하다

 

결론적으로, JPA Repo의 단위 테스트가 필요하다면 @DataJpaTest를 사용하는게 효과적이다.

회원 생성 후 검증 코드예시

@RunWith(SpringRunner.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
public class UserJpaRepoTest {

    @Autowired
    private UserJpaRepo userJpaRepo;
    @Autowired
    private PasswordEncoder passwordEncoder;

    private String name = "woonsik";
    private String email = "dnstlr2933@naver.com";
    private String password = "myPassWord";


    @Test
    public void 회원저장_후_이메일로_회원검색() throws Exception {

        //given
        userJpaRepo.save(User.builder()
                .name(name)
                .email(email)
                .password(passwordEncoder.encode(password))
                .nickName(name)
                .roles(Collections.singletonList("ROLE_USER"))
                .build());

        //when
        User user = userJpaRepo.findByEmail(email).orElseThrow(CUserNotFoundException::new);

        //then
        assertNotNull(user);
        assertEquals(user.getUsername(), user.getUsername());
        assertThat(user.getName()).isEqualTo(name);
        assertThat(user.getNickName()).isEqualTo(name);
    }
}

JPA를 사용하지 않는다면 @JdbcTest를 사용하면 된다.


2. MVC (컨트롤러) 테스트를 위한 @WebMvcTest

컨트롤러가 예상대로 동작하는지를 위한 테스트이다.

 

아래 내용만 스캔해서 로딩한다

@Controller, @ControllerAdvice, @JsonComponent,

Converter, GenericConverter, Filter, HandlerInterceptor, WebMvcConfigurer, HandlerMethodArgumentResolver

---

@Service, @Component, @Repository는 스캔하지 않는다

  • MockBean, MovkMVC를 자동 구성하여 테스트 가능하도록 한다.
  • 스프링 시큐리티에 대한 테스트도 지원한다.
  • 테스트할 특정 컨트롤러 클래스를 명시하도록 한다.

 

Controller
@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(){
        return "hello";
    }
}
테스트 코드 
@RunWith(SpringRunner.class)
@WebMvcTest
public class HelloControllerTest {

    @Autowired
    private MockMvc mvc;

    @Test
    public void testHello() throws Exception{
        String hello = "hello";

        mvc.perform(get("/hello"))
            .andExpect(status().isOk())
            .andExpect(content().string(hello));
    }
}

3. REST 클라이언트 테스트를 위한 @RestClientTest

@RestClientTest를 사용하면 REST 클라이언트 테스트가 가능하다.

  • REST API 통신이 JSON 형식의 데이터를 예상대로 반환하는지 테스트하게 된다.

 

예를들어, Apache HttpClient or Spring RestTemplate을 사용하여 외부 서버에 웹 요청을 보내는 경우이에 응답할 Mock서버를 만들어주는 것이라고 생각하면 된다.

 

테스트 코드

@RunWith(SpringRunner.class)
@RestClientTest(RestUserService.class)
public class UserServiceMockTest {

    @Autowired
    private RestUserService userService;
    @Autowired
    private MockRestServiceServer server;

    @Test
    public void 회원가져오기() throws Exception {
        //given
        server
                .expect(MockRestRequestMatchers
                        .requestTo("/v1/user/email/dnstlr2933@naver.com"))
                .andRespond(MockRestResponseCreators
                        .withSuccess(
                                new ClassPathResource("/test.json", getClass()),
                                MediaType.APPLICATION_JSON)
                );

        //when
        User byEmail = userService.getUserByEmail("dnstlr2933@naver.com");

        //then
        Assertions.assertThat(byEmail.getEmail()).isEqualTo("dnstlr2933@naver.com");
        Assertions.assertThat(byEmail.getName()).isEqualTo("woonsik");
        server.verify();
    }
}

컨트롤러에서 처리하는 URL로 요청을 보내고 성공시 test.json파일에 정의한 값으로 응답을 준다. 응답값과 정의한 값을 비교

test.json 파일
{
  "email": "dnstlr2933@naver.com",
  "name": "woonsik"
}
RestUserService 클래스
@Service
public class RestUserService {

    private final RestTemplate restTemplate;

    public RestUserService(RestTemplateBuilder restTemplateBuilder) {
        this.restTemplate = restTemplateBuilder.build();
    }

    public User getUserByEmail(String email) {
        return restTemplate.getForObject("/v1/user/email/{email}", User.class, email);
    }
}

 

❗️해당 테스트를 할 때 주의사항❗️

Caused by: org.springframework.beans.factory.BeanCreationException: 
	Error creating bean with name 'jpaMappingContext': 
		Invocation of init method failed; nested exception is java.lang.IllegalArgumentException:
			JPA metamodel must not be empty!

@WebMvcTest로 테스트를 진행하던 도중 위와 같은 에러가 발생했는데, 결론은 JPA metamodel must not be empty! 로, JPA 메타 모델은 비워둘 수 없다고 알려주는 것이다.

 

@WebMvcTest는 JPA 생성과 관련된 기능이 전혀 존재하지 않는 테스트 어노테이션이다.

 

그런데 어떤 테스트를 진행하던간에 booststrapping클래스는 항상 로딩되므로 @EnableJpaAuditing이 붙어있는 bootstrpping클래스를 로딩하게 된다.

현재 JPA Auditing 기능을 사용하고 있으므로 @SpringBootApplication @EnableJPaAuditing을 추가하여 사용하고 있어서 로딩하게 되는데 JPA 관련 빈은 로딩하지 않으니깐 에러가 발생하게 되는 것이다.

@EnableJpaAuditing
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

 

해결방법

간단하게 해결이 가능하다.

 

@EnableJpaAditing을 분리해주면 된다

@EnableJpaAuditing
@Configuration
public class JpaAuditingConfiguration {
}

 

반응형
Comments
반응형
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday