RestApi 만들기 - 입력값 제한하기 + 입력값 이외의 에러 발생 (3)

반응형
반응형

API를 만들다보면,
입력값을 제한하고 싶은 경우가 발생합니다.
예를들면,

public class Delivery {
    private int id;
    private String item;
    private String user;
    private LocalDateTime deliveryTime;
    private LocalDateTime deliveryEndTime;
    private DeliveryStatus status;
    private Integer itemPrice;
    private Integer deliveryCost;
}

이런 객체가 있다고 가정해봅시다.
그런데, id같은 경우 jpa에서 번호를 정해주고,
deliveryStatus는  현재 배달의 진행을 보고 결정 되고,
deliveryCost는 itemPrice의 값을 보고 결정한다고 가정해본다면,

두 값은 입력을 받아서는 안되는 값들입니다.
왜냐하면 외부에서 그 값들을 결정하기 때문입니다.

테스트 코드를 작성해봅시다.

    @Test
        void create_Delivery() throws Exception {
            Delivery delivery = Delivery.builder()
                    .id(100)
                    .item("book")
                    .user("klom")
                    .build();
            Mockito.when(deliveryRepository.save(delivery)).thenReturn(delivery);
            mockMvc.perform(post("/api/delivery/")
                        .accept(MediaTypes.HAL_JSON_VALUE)
                        .contentType(MediaType.APPLICATION_JSON_VALUE)
                        .content(objectMapper.writeValueAsString(delivery))
                    )
                   .andDo(print())
                   .andExpect(status().isCreated())
                   .andExpect(jsonPath("id").value(Matchers.not(100)))
                   .andExpect(jsonPath("status").value(DeliveryStatus.READY))
                   .andExpect(jsonPath("deliveryCost").value(5000))
            ;
        }

id값이 100입니다.
저희는 id값은 자동으로 증가되기 원하고 있습니다. 따라서 위 코드는 201가 나와야 됩니다.
하지만 
결과는 테스트 실패입니다.

그러면 어떻게 해야 될까요?
방법은 여러가지 입니다.

첫 번째 방법은 
@JsonIgnore어노테이션을 붙이면 해결되는 경우입니다.

public class Delivery {
    @Id
    @GeneratedValue
    private int id;
    private String item;
    private String user;
    private LocalDateTime deliveryTime;
    private LocalDateTime deliveryEndTime;
    @JsonIgnore
    private DeliveryStatus status;
    private Integer itemPrice;
    @JsonIgnore
    private Integer deliveryCost;
}


id는 null이 발생해서 일단 붙이지 않았습니다;;
이것을 해결하는 방법은 이따 설명하겠습니다.

그러면

Body = {"id":100,"item":"book","user":"klom","deliveryTime":null,"deliveryEndTime":null,"itemPrice":null}


 @JsonIgnore이 붙어있는 곳은 json으로 만들어지지 않는다는 것을 알 수 있습니다.
그렇다면 json도 유지하면서 에러를 성공하는 방법은 없을까요?
dto를 만드는 방법입니다.

dto는 api를 위해 만들어지는 객체라고 생각하면 될것 같습니다.

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class DeliveryDto {
    private String item;
    private String user;
    private LocalDateTime deliveryTime;
    private LocalDateTime deliveryEndTime;
    private Integer itemPrice;
}

이렇게 만들구,

이제 입력을 deliveyDto로 받게 하겠습니다.
근데 문제가 발생했습니다.

dto로 받기 때문에 컴파일에러가 발생했습니다.
이럴때는 어떻게 해야될까요?

여기에도 몇 가지 방법이 존재합니다.

첫 번째 방법 builder을 이용해서 값을 바인딩한다.

Delivery deliver = Delivery.builder()
                .item(deliveryDto.getItem())
                .user(deliveryDto.getUser())
                .build();

이렇게 값을 DeliveryDto에서 delivery로 변경되었습니다.
물론 테스트는 실패입니다. 추후에 설명하겠습니다.

두 번째 방법은 Modelmapper을 이용하는 방법입니다.

Modelmapper는 서로 다른 객체를 바인딩해주는 라이브러리입니다.
이 라이브러리는 스프링ioc에서 관리해주지 않기 때문에 직접 빈으로 등록해줘야합니다.
아니면 그냥 쓰던가.

그러면

Delivery deliver = modelMapper.map(deliveryDto, Delivery.class);

단 한줄로 완성을 시킬 수 있습니다.
리플렉션 기술을 사용하기 때문에 좀 그렇다 하는 분들은 사용하지 않는것을 추천드립니다. 그냥 빌더 쓰세요.
자바 버전이 올라가면서 리플렉션 기술도 그리 느리지는 않는다고 합니다.

이제 

이 오류를 해결해봅시다.

테스트 코드를 다시 봅시다.

가장 큰 문제는

Mockito.when(deliveryRepository.save(delivery)).thenReturn(delivery);

바로 이 녀석입니다.
이것이 문제인 이유는 
컨트롤러에서는 deliveryDto를 받고 있습니다.

그것을 delivery로 바꾸는 작업을 가졌습니다.
근데 생각해야 될것이 이 바꾼 delivery와 테스트의 delivery는 같은 객체일까요?

위 코드는 테스트의 delivery를 말하고 있습니다.
즉, 같은 주소값이 들어가야 위 코드가 정상적으로 동작한다는 뜻입니다.

컨트롤러의 delivery : restapiset.Delivery@3b
테스트의 delivery : restapiset.Delivery@9f


그러면 어떻게 해야 될까요?

여기에도 여러 가지방법이 있습니다.

첫 번째 방법은 모든 delivey객체를 save한다면 delivery를 리턴시키면 됩니다.

import static org.mockito.ArgumentMatchers.any;

Mockito.when(deliveryRepository.save(any(Delivery.class))).thenReturn(delivery);

생각해될것이 모든 delivery객체를 허용할까입니다.
과연 이렇게 해도 좋을까?

두 번째 방법은 슬라이싱 테스트의 위치를 스프링부트 테스트로 수정하는 것입니다.
하지만 문제는 mockMvc를 사용이 불가능합니다.

@SpringBootTest
@AutoConfigureMockMvc
class DeliveryTest {

이걸 붙이는 순간 mockMvc를 사용할 수있게 됩니다.

이렇게 되면 더 이상 목빈은 필요없습니다.
왜냐하면 테스트에서 save할 이유가 없기 때문입니다.

마지막으로 delivery를 수정합시다.

public class Delivery {
    @Id
    @GeneratedValue
    private int id;
    private String item;
    private String user;
    private LocalDateTime deliveryTime;
    private LocalDateTime deliveryEndTime;
    private DeliveryStatus status = DeliveryStatus.READY;
    private Integer itemPrice;
    private Integer deliveryCost = 5000;
}

초기값을 이렇게 세팅해 두고,

spring.jackson.deserialization.fail-on-unknown-properties=true


테스트를 하게 되면 잘못된 값이 들어오면 400에러가 발생합니다.

 

 

 

 

 

 

 

반응형

댓글

Designed by JB FACTORY