[横書きアイテム]そんなREST APIで大丈夫ですか?本格的なREST APIの実装:イベントコントローラとイベントアクチュエータHandler

41624 ワード

EventControlテストの作成


テストケースは次のとおりです.
  • が正常EventDtoを送信と、データベースに挿入するid値を含むEventに戻るか、
  • に戻るか.
    エラーのEventDto
  • が送信されたときにエラーのErrorコードが返されるかどうか.
  • 注意事項
  • mockMvcにコンテンツを含める場合、Json形式でコンテンツを含める-objectMapperは
  • を使用します.
  • ErrorコードはInterceptorを使用して返されます.
  • 現在、サービス層はありません.コントローラは一時的にRepositoryを使用します.
  • package com.carrykim.restapi.event;
    
    import com.carrykim.restapi.event.model.dto.EventDto;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.hateoas.MediaTypes;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.MediaType;
    import org.springframework.test.web.servlet.MockMvc;
    
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
    import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
    
    @SpringBootTest
    @AutoConfigureMockMvc
    public class EventControllerTest {
    
        @Autowired
        private MockMvc mockMvc;
    
        @Autowired
        private ObjectMapper objectMapper;
    
    
        private EventDto createEventDto(){
            return EventDto.builder()
                    .name("My Event")
                    .description("This is my first Event")
                    .build();
        }
    
        @Test
        public void create_event_success() throws Exception {
            //Given
            EventDto eventDto = createEventDto();
    
            //When
            //Then
            mockMvc.perform(post("/api/events")
                            .contentType(MediaType.APPLICATION_JSON)
                            .accept(MediaTypes.HAL_JSON)
                            .content(objectMapper.writeValueAsString(eventDto)))
                    .andDo(print())
                    .andExpect(status().isCreated())
                    .andExpect(jsonPath("id").exists())
                    .andExpect(header().exists(HttpHeaders.LOCATION))
                    .andExpect(header().string(HttpHeaders.CONTENT_TYPE,MediaTypes.HAL_JSON_VALUE));
        }
    
        @Test
        public void create_event_bad_request_empty_input() throws Exception {
            //Given
            EventDto eventDto = EventDto.builder().build();
    
            //When
            //Then
            mockMvc.perform(post("/api/events")
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(eventDto)))
                    .andDo(print())
                    .andExpect(status().isBadRequest())
                    .andExpect(jsonPath("timestamp").exists())
                    .andExpect(jsonPath("status").exists())
                    .andExpect(jsonPath("error").exists())
                    .andExpect(jsonPath("message").exists());
        }
    }
    

    Event Controlの作成

    package com.carrykim.restapi.event.controller;
    
    import com.carrykim.restapi.event.model.Event;
    import com.carrykim.restapi.event.infra.EventRepository;
    import com.carrykim.restapi.event.model.dto.EventDto;
    import org.springframework.hateoas.MediaTypes;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.validation.Valid;
    import java.net.URI;
    
    import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
    import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
    
    @RestController()
    @RequestMapping(value = "/api/events", produces = MediaTypes.HAL_JSON_VALUE)
    public class EventController {
    
        private final EventRepository eventRepository;
    
        public EventController(EventRepository eventRepository) {
            this.eventRepository = eventRepository;
        }
    
        @PostMapping("")
        public ResponseEntity create(@RequestBody @Valid EventDto eventDto) {
            Event newEvent = this.eventRepository.save(eventDto.toModel());
            URI uri = linkTo(methodOn(EventController.class).create(new EventDto())).slash(newEvent.getId()).toUri();
            return ResponseEntity.created(uri).body(newEvent);
        }
    }
    

    ErrorResponseの作成


  • 応答でJsonマッチングを自動的に使用するには、Getterを宣言する必要があります!
    -Requestもそうです->Lombok Getter Notation
  • を使用
    package com.carrykim.restapi.event.global.exception;
    
    import lombok.Builder;
    import lombok.Getter;
    import org.springframework.http.HttpStatus;
    import org.springframework.stereotype.Component;
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.FieldError;
    
    import java.time.LocalDateTime;
    import java.util.List;
    import java.util.stream.Collectors;
    
    @Getter
    public class ErrorResponse {
    
        private final LocalDateTime timestamp = LocalDateTime.now();
        private int status;
        private List<String> message;
        private String error;
    
        public ErrorResponse(HttpStatus httpStatus , BindingResult bindingResult) {
            this.status = httpStatus.value();
            this.error = httpStatus.name();
            this.message = bindingResult.getFieldErrors().stream()
                    .map(e -> {
                        String m = e.getField() + " : " + e.getDefaultMessage();
                        return m;
                    })
                    .collect(Collectors.toList());
        }
    
    }
    

    EventExceptionHandlerの作成

    package com.carrykim.restapi.event.global.exception;
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.hateoas.MediaTypes;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    
    @RestControllerAdvice
    public class EventExceptionHandler {
    
        @ExceptionHandler(MethodArgumentNotValidException.class)
        public ResponseEntity validationErrorException(final MethodArgumentNotValidException e) throws JsonProcessingException {
    
            return ResponseEntity
                    .status(HttpStatus.BAD_REQUEST)
                    .body(new ErrorResponse(HttpStatus.BAD_REQUEST,e.getBindingResult()));
        }
    }