문제 상황 (1)
Error creating bean with name 'jpaAuditingHandler': Cannot resolve reference to bean 'jpaMappingContext' while setting constructor argument;
- jpaAuditingHandler 빈이 생성될 때 발생하는 에러인거 같습니다.
- JPA metamodel must not be empty! 이런 메시지도 같이 나오게 됩니다.
@WebMvcTest(controllers = ProductController.class)
class ProductControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@MockBean
private ProductService productService;
@DisplayName("신규 상품을 등록한다.")
@Test
void createProduct() throws Exception {
// given
ProductCreateRequest request = ProductCreateRequest.builder()
.type(ProductType.HANDMADE)
.sellingStatus(ProductSellingStatus.SELLING)
.name("아메리카노")
.price(4000)
.build();
// when & then
mockMvc.perform(post("/api/v1/products/new")
.content(objectMapper.writeValueAsString(request))
.contentType(APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
- 문제가 생기는 테스트 코드입니다.!
해결 (1)
- 참고자료: https://velog.io/@suujeen/Error-creating-bean-with-name-jpaAuditingHandler
- 두 가지 방법이 있는데, 참고자료 블로그를 참고하시길~
- 두번째 방법을 선택했습니다.
- 첫번째 방법의 단점으로는 WebMvcTest를 진행할 때마다 번거로운 작업을 하게 된다는 점이였습니다.
EnableJpaAuditing -> JPA관련 빈으로 @WebMvcTest 어노테이션을 붙인 경우에서는 주입받지 않게 됩니다.
@WebMvcTest 어노테이션은 Controller를 테스트하기 위한 어노테이션입니다.
즉, 단위 테스트를 하기에 적합합니다. Web과 관련된 의존성만 주입받기 때문!
관련된 빈 목록
- @Controller
- @ControllerAdvice
- @JsonComponent, Converter
- @GenericConverter
- @Filter
- @HandlerInterceptor
@EnableJpaAuditing을 사용하면 @EnableJpaAuditing 기능이 활성화되면서 JPA관련 빈들을 찾으려고 합니다.
즉, 여기서 에러가 터진다는 점!
분리합시다.
Before
@EnableJpaAuditing
@SpringBootApplication
public class CafekioskApplication {
public static void main(String[] args) {
SpringApplication.run(CafekioskApplication.class, args);
}
}
After
@SpringBootApplication
public class CafekioskApplication {
public static void main(String[] args) {
SpringApplication.run(CafekioskApplication.class, args);
}
}
@EnableJpaAuditing
@Configuration
public class JpaAuditingConfig {
}
- 분리하므로써, EnableJpaAuditing이 테스트 코드 수행할 때 실행되지 않게 됩니다.
- 개발 코드는 @Configuration 안을 들어가보면 @Component 어노테이션이 붙어있기에 스캔하게 되면서 EnableJpaAuditing역시 수행됩니다.
문제 상황 (2)
- 위의 테스트 코드를 자신만만하게 돌렸습니다..!
- 뭘까요...
- 400이면 Bad Request인데??
해결 (1)
@PostMapping("/api/v1/products/new")
public ResponseEntity<ProductResponse> createProduct(ProductCreateRequest request) {
return ResponseEntity.ok(productService.createProduct(request));
}
- 이런 @RequestBody를 써주지 않았습니다.
- Json 객체를 받을 때 @RequestBody를 써줘야 하는데
@PostMapping("/api/v1/products/new")
public ResponseEntity<ProductResponse> createProduct(@RequestBody ProductCreateRequest request) {
return ResponseEntity.ok(productService.createProduct(request));
}
이번엔 자신만만하지 않게 돌렸습니다.
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class sample.cafekiosk.spring.api.controller.product.dto.request.ProductCreateRequest]; nested exception is cohttp://m.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `sample.cafekiosk.spring.api.controller.product.dto.request.ProductCreateRequest` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 2]
- 에러 메시지를 읽어보니, ProductCreateRequest에 Binding될때 생기는 문제로, ProductCreateRequest 생성자로 인스턴스를 만들지 못하고 있다는거 같습니다.
- 기본 생성자를 만들어달라고 하네요!
@Getter
@NoArgsConstructor
public class ProductCreateRequest {
private ProductType type;
private ProductSellingStatus sellingStatus;
private String name;
private int price;
/* getter setter builder */
}
- ObjectMapper로 Json String으로 직렬화되어 들어온 값을 객체를 매핑(역직렬화)를 해줘야 하는데 그 과정에서 기본 생성자를 사용한다고 하네요.! -> 이게 이유
해결했습니다.!!