REST API -이벤트 조회 및 수정 REST API 개발

박상훈·2022년 4월 29일
0
post-thumbnail

4-1.이벤트 목록 조회 API 구현


Pageable : JPA Repository 로 조회할 때 페이징 작업
PagedResourcesAssembler : 페이지와 리소스를 EntityModel 로 변환
ㄴ 강의 영상에서 toResource() 사용 -> 버전 문제로 toModel 사용하여 문제 해결

*Event Controller 코드 추가

@GetMapping
public ResponseEntity queryEvents(Pageable pageable, PagedResourcesAssembler<Event> pagedResourcesAssembler) {
    Page<Event> page = this.eventRepository.findAll(pageable);
    var entityModels = pagedResourcesAssembler.toModel(page, EventResource::new);
    entityModels.add(Link.of("/docs/index.html#resources-events-list").withRel("profile"));
    return ResponseEntity.ok(entityModels);
}

Event Test 코드 추가

@Test
@DisplayName("30개의 이벤트를 10개씩 두번째 페이지 조회")
public void queryEvents() throws Exception {
    //Given
    IntStream.range(0, 30).forEach(this::generateEvent);

    mockMvc.perform(get("/api/events")
                    .param("page", "1")
                    .param("size", "10")
                    .param("sort", "name,DESC"))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(jsonPath("page").exists())
            .andExpect(jsonPath("_embedded.eventList[0]._links.self").exists())
            .andExpect(jsonPath("_links.self").exists())
            .andExpect(jsonPath("_links.profile").exists())
            .andDo(document("query-events"));
}

private void generateEvent(int index) {
    Event event = Event.builder()
            .name("event " + index)
            .description("test event")
            .build();

    this.eventRepository.save(event);
}

4-2.이벤트 조회 API 구현


생성과 동일한 방식으로 이벤트 핸들러 추가, profile 링크 추가 테스트 코드에 document 추가

Event Controller 코드 추가

@GetMapping("/{id}")
public ResponseEntity getEvent(@PathVariable Integer id) {
    Optional<Event> optEvent = this.eventRepository.findById(id);
    if (optEvent.isEmpty()) {
        return ResponseEntity.notFound().build();
    }

    Event event = optEvent.get();
    EventResource eventResource = new EventResource(event);
    eventResource.add(Link.of("/docs/index.html#resources-events-get").withRel("profile"));
    return ResponseEntity.ok(eventResource);
}

Event Test 코드 추가

id 값으로 조회 되거나 되지 않은 경우 2가지 테스트

@Test
@DisplayName("1개 조회")
public void getEvent() throws Exception {
    Event event = generateEvent(100);

    this.mockMvc.perform(get("/api/events/{id}", event.getId()))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(jsonPath("_links.self").exists())
            .andExpect(jsonPath("_links.profile").exists())
            .andDo(document("get-an-event"));
}

@Test
@DisplayName("잘못된 ID로 조회시 not found 응답")
public void notFount() throws Exception {
    this.mockMvc.perform(get("/api/events/12345"))
            .andExpect(status().isNotFound());
}

4-3.이벤트 수정 API 구현


생성과 동일한 방식으로 이벤트 핸들러 추가, profile 링크 추가 테스트 코드에 document 추가

Event Controller 코드 추가

@PutMapping("/{id}")
public ResponseEntity updateEvent(@PathVariable Integer id, @RequestBody @Validated EventDto eventDto, Errors errors) {
    Optional<Event> optionalEvent = this.eventRepository.findById(id);
    if (optionalEvent.isEmpty()) {
        return badRequest(errors);
    }

    if (errors.hasErrors()) {
        return badRequest(errors);
    }

    this.eventValidator.validate(eventDto, errors);
    if (errors.hasErrors()) {
        return badRequest(errors);
    }

    Event event = optionalEvent.get();
    modelMapper.map(eventDto, event);
    Event updatedEvent = this.eventRepository.save(event);
    EventResource eventResource = new EventResource(updatedEvent);
    eventResource.add(Link.of("/docs/index.html#resources-events-update").withRel("profile"));
    return ResponseEntity.ok(eventResource);
}

Event Test 코드 추가

@Test
@DisplayName("정상적으로 데이터 수정")
public void updateEvent() throws Exception {
    Event event = this.generateEvent(200);
    EventDto eventDto = this.modelMapper.map(event, EventDto.class);
    String eventName = "Updated Event";
    eventDto.setName(eventName);

    this.mockMvc.perform(put("/api/events/{id}", event.getId())
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(this.objectMapper.writeValueAsString(eventDto)))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(jsonPath("name").value(eventName))
            .andExpect(jsonPath("_links.self").exists());
}

@Test
@DisplayName("Event id 가 없는 데이터를 호출한 경우")
public void updateEvent404() throws Exception {
    Event event = this.generateEvent(200);
    EventDto eventDto = this.modelMapper.map(event, EventDto.class);
    String eventName = "Updated Event";
    eventDto.setName(eventName);

    this.mockMvc.perform(put("/api/events/{id}", 12345)
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(this.objectMapper.writeValueAsString(eventDto)))
            .andDo(print())
            .andExpect(status().isBadRequest());
}

@Test
@DisplayName("수정하려는 데이터가 없는 데이터인 경우")
public void updateEvent400Empty() throws Exception {
    Event event = this.generateEvent(200);
    EventDto eventDto = new EventDto();

    this.mockMvc.perform(put("/api/events/{id}", event.getId())
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(this.objectMapper.writeValueAsString(eventDto)))
            .andDo(print())
            .andExpect(status().isBadRequest());
}

@Test
@DisplayName("수정하려는 데이터 정상적이지 않은 데이터인 경우")
public void updateEvent400Wrong() throws Exception {
    Event event = this.generateEvent(200);
    EventDto eventDto = this.modelMapper.map(event, EventDto.class);
    eventDto.setBasePrice(20000);
    eventDto.setMaxPrice(100);

    this.mockMvc.perform(put("/api/events/{id}", event.getId())
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(this.objectMapper.writeValueAsString(eventDto)))
            .andDo(print())
            .andExpect(status().isBadRequest());
}

private Event generateEvent(int index) {
    Event event = Event.builder()
            .name("event " + index)
            .description("REST API")
            .beginEnrollmentDateTime(LocalDateTime.of(2022, 3, 27, 11, 30))
            .closeEnrollmentDateTime(LocalDateTime.of(2022, 3, 28, 11, 30))
            .beginEventDateTime(LocalDateTime.of(2022, 4, 27, 11, 30))
            .endEventDateTime(LocalDateTime.of(2022, 4, 28, 11, 30))
            .basePrice(100)
            .maxPrice(200)
            .limitOfEnrollment(100)
            .location("강남역")
            .free(false)
            .offline(true)
            .eventStatus(EventStatus.DRAFT)
            .build();

    this.eventRepository.save(event);
    return event;
}

4-4.테스트 코드 리팩토링


공통으로 사용하는 annotation, autowired field 새로운 클래스 생성해서 옮기고
기존에 사용하던 EventControllerTests, IndexControllerTest 상속받아서 사용하기
강의 영상에서 사용하던 Junit4 @Ignore -> Junit5 @Disabled 대체하여 해결

부모 클래스 생성

@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureRestDocs
@Import(RestDocsConfiguration.class)
@ActiveProfiles("test")
@Disabled
public class BaseControllerTest {
    @Autowired
    protected MockMvc mockMvc;

}

기존에 사용하던 클래스 코드 수정

public class EventControllerTests extends BaseControllerTest {
	...생략
}

public class IndexControllerTest extends BaseControllerTest {
	...생략
}
profile
엔지니어

0개의 댓글