순간을 성실히, 화려함보단 꾸준함을

JPA metamodel must not be empty!!!!!! 본문

나의 개발 메모장

JPA metamodel must not be empty!!!!!!

폭발토끼 2023. 1. 18. 22:34

안녕하세요
오랜만에 찾아왔습니다.

현재 인프런 호돌맨님의 '요절복통 개발쇼' 강의를 보면서 개인적으로 공부하면서 코딩하고 있는데 마주쳤던 에러를 하나 소개해드릴려고 합니다.

JPA metamodel must not be empty! 입니다.

우리가 JPA 를 사용하여 프로젝트를 진행할때 도메인(엔티티) 를 생성할시에 '생성시간','수정시간' 을 필드로 정의하게 됩니다.
이때, 모든 도메인(엔티티)에 적용해야 하므로 @MappedSuperclass를 사용해서 공통 클래스를 정의해주죠?

@Getter @Setter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {

    /**
     * @MpaaerSuperclass : 공통 매핑 정보가 필요할때 사용한다. createDate 와 modifiedDate는 지속적으로 사용되는 변수여서 BaseEntity 로 선언한 뒤 상속을 받게 한다.
     * 조회 및 검색이 불가하고 직접 생성해서 사용할 일이 없기 때문에 추상 클래스(abstract class)로 만드는 것이 좋다.
     */

    /**
     * @EntityListeners(AuditingEntityListener.class) : Spring Data JPA 에서 지원해주는 기능으로 생성일,수정일과 같은 기록들을 편리하게 관리할 수 있도록 지원해준다.
     * Audit 기능 : spring data jpa 에서 시간에 대한 정보들을 자동으로 넣어주는 기능
     * @CreatedDate : Entity가 생성되어 저장될 때 시간이 자동 저장
     * @LastModifiedDate : 조회한 Entity의 값을 변경할 때 시간이 자동 저장
     */

    @CreatedDate
    private LocalDateTime createDate;

    @LastModifiedDate
    private LocalDateTime modifiedDate;
}

또한, @EntityListeners(AuditingEntityListener.class) 어노테이션을 사용해서 Auditing 기능을 활성화 시켜주었습니다.

이 Auditing 기능을 인식하기 위해서 ---Application 에 @EnableJpaAuditing 어노테이션을 정의해주어야 합니다.

/**
 * @EnableJpaAuditing : JPA Auditing 기능을 활성화 시켜주기 위해 적용
 */
@EnableJpaAuditing
@SpringBootApplication
public class EunstargramApplication {

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

}

참고 링크 : https://wildeveloperetrain.tistory.com/76
(위 링크를 꼭 확인해주시고 공부!!!)

그럼 이런 상태에서 api 를 테스트 하기 위해 테스트 코드를 짰습니다. Controller layer 를 테스트 하려고 합니다.

@WebMvcTest(MemberController.class)
class MemberControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private MemberService memberService;

    @MockBean
    private MemberJpaRepository memberJpaRepository;

    private static final String COMMON_URL="/api/member";

    @Test
    @DisplayName("/signUp 요청시 200 status code 리턴")
    void signUpTest() throws Exception {
        //expected
        mockMvc.perform(MockMvcRequestBuilders.post(COMMON_URL + "/signUp")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"memberId\" : \"testId\"," +
                                "\"password\" : \"1234\"," +
                                "\"nickName\" : \"Rabbit96\"," +
                                "\"phoneNumber\" : \"010-1111-2222\"," +
                                "\"birthDay\" : \"20220107\"," +
                                "\"intro\" : \"AA\"," +
                                "\"imagePath\" : \"location/1234\"," +
                                "\"cancelYN\" : \"N\"}")
                )
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print());

        assertThat(memberJpaRepository.count()).isEqualTo(1);
    }
}

이 테스트를 실행하면 잘 될까요????
안타깝게도 제목에 적은 에러가 발생합니다. 왜 이런 에러가 발생할까요?

---Application 에 정의한 @EnableJpaAuditing 와 Test Code 에 정의한 @WebMvcTest 어노테이션 때문입니다.
@EnableJpaAuditing 어노테이션은 JPA 에 관한 Bean입니다.
그러나 @WebMvcTest 는 JPA 관련 Bean 들을 로드하지 않습니다.
(@WebMvcTest 는 대표적으로 @Controller , @ControllerAdvice 어노테이션을 Bean 으로 로드합니다)

그래서 작성한 Test Code 에서 빈을 로드할 수 없기 때문에 JPA metamodel must not be empty! 에러가 발생하는 것이죠.

그럼 어떻게 해결할 수 있을까요??

해결방법 링크 : https://jeongkyun-it.tistory.com/199
(@WebMvcTest 를 유지한체로 해결하는 방법은 첨부해드린 링크를 참고하면 될 것 같습니다.)

전 슬라이스 테스트를 통합 테스트로 한번 변경해 보았습니다.
즉, @WebMvcTest@SpringBootTest 변경하였습니다.

//@WebMvcTest(MemberController.class)
@SpringBootTest
class MemberControllerTest {

    @Autowired
    private MockMvc mockMvc;

    //@MockBean
    @Autowired
    private MemberService memberService;

    //@MockBean
    @Autowired
    private MemberJpaRepository memberJpaRepository;

    private static final String COMMON_URL="/api/member";

    @Test
    @DisplayName("/signUp 요청시 200 status code 리턴")
    void signUpTest() throws Exception {
        //expected
        mockMvc.perform(MockMvcRequestBuilders.post(COMMON_URL + "/signUp")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"memberId\" : \"testId\"," +
                                "\"password\" : \"1234\"," +
                                "\"nickName\" : \"Rabbit96\"," +
                                "\"phoneNumber\" : \"010-1111-2222\"," +
                                "\"birthDay\" : \"20220107\"," +
                                "\"intro\" : \"AA\"," +
                                "\"imagePath\" : \"location/1234\"," +
                                "\"cancelYN\" : \"N\"}")
                )
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print());

        assertThat(memberJpaRepository.count()).isEqualTo(1);
    }
}

@SpringBootTest 를 붙여줬습니다. 자!!이제 테스트를 실행해보려고 했더니....????

Could not autowire. No beans of 'MockMvc' type found.

이런 에러가 발생합니다. MockMvc 객체를 주입받지 못하는 상황인거죠. 한번 @WebMvcTest 를 들어가 볼까요?

@AutoConfigureMockMvc 어노테이션을 붙여주면 됩니다!!!
(@WebMvcTest@AutoConfigureMockMvc 의 차이점이 무엇인지 공부!!)

//@WebMvcTest(MemberController.class)
@AutoConfigureMockMvc
@SpringBootTest
class MemberControllerTest {

    @Autowired
    private MockMvc mockMvc;

    //@MockBean
    @Autowired
    private MemberService memberService;

    //@MockBean
    @Autowired
    private MemberJpaRepository memberJpaRepository;

    private static final String COMMON_URL="/api/member";

    @Test
    @DisplayName("/signUp 요청시 200 status code 리턴")
    void signUpTest() throws Exception {
        //expected
        mockMvc.perform(MockMvcRequestBuilders.post(COMMON_URL + "/signUp")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"memberId\" : \"testId\"," +
                                "\"password\" : \"1234\"," +
                                "\"nickName\" : \"Rabbit96\"," +
                                "\"phoneNumber\" : \"010-1111-2222\"," +
                                "\"birthDay\" : \"20220107\"," +
                                "\"intro\" : \"AA\"," +
                                "\"imagePath\" : \"location/1234\"," +
                                "\"cancelYN\" : \"N\"}")
                )
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print());

        assertThat(memberJpaRepository.count()).isEqualTo(1);
    }
}

오늘의 일지 끝!!!