똑같은 삽질은 2번 하지 말자

Spring으로 REST API No.2(도메인구현, 기본 요청 응답(201)테스트, ) 본문

Spring/Spring Boot

Spring으로 REST API No.2(도메인구현, 기본 요청 응답(201)테스트, )

곽빵 2020. 6. 28. 16:19

도메인 구현

@Builder 
@AllArgsConstructor @NoArgsConstructor // public default 생성자
@Getter @Setter // @Data에는 EqualsAndHashCode가 기본적으로 정의되어있어서 겹침
@EqualsAndHashCode(of = "id") 
// 기본적으로 객체의 모든 필드로 Equals, HashCode를 비교하는데 그럼 
// 나중에 연관관계에서 (상호참조하는) StackOverFlow가 발생할 수 있으므로 ID로만 비교한다.

public class Event {
	private Integer id;
	private String name;
	private String description;
	private LocalDateTime beginEnrollmentDateTime;
	private LocalDateTime closeEnrollmentDateTime;
	private LocalDateTime beginEventDateTime;
	private LocalDateTime endEventDateTime;
	private String location; // (optional) 이게 없으면 온라인 모임
	private int basePrice;
	private int maxPrice;
	private int limitOfEnrollment;
	private boolean offline;
	private boolean free;
	private EventStatus eventStatus;
}

테스트

테스트 할 것

  • 입력값들을 전달하면 JSON 응답으로 201이 나오는지 확인.
  •  Location 헤더에 생성된 이벤트를 조회할 수 있는 URI 담겨 있는지 확인.
  •  id DB에 들어갈 때 자동생성된 값으로 나오는지 확인

ControllerTest

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.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.time.LocalDateTime;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import com.fasterxml.jackson.databind.ObjectMapper;

@RunWith(SpringRunner.class)
@WebMvcTest // slicing testing
public class EventControllerTests {
	@Autowired
	MockMvc mockMvc; // 요청 응답 을 만들어 줄 수 있는
	
	@Autowired
	ObjectMapper objectMapper;
	
	@MockBean
	EventRepository eventRepository; // 슬라이싱 테스트라 레포짓을 목으로 넣어준다.
	
	@Test
	public void createEvent() throws Exception {
		Event event = Event.builder()
				.name("Spring")
				.description("REST API")
				.beginEnrollmentDateTime(LocalDateTime.of(2020,6,28,14,11))
				.closeEnrollmentDateTime(LocalDateTime.of(2020,6,28,14,11))
				.beginEventDateTime(LocalDateTime.of(2020,6,28,14,11))
				.endEventDateTime(LocalDateTime.of(2020,6,28,14,11))
				.basePrice(100)
				.maxPrice(200)
				.limitOfEnrollment(100)
				.location("부산역 어딘가")
				.build();
		event.setId(10); // Mock은 null로 리턴되기 때문에
		Mockito.when(eventRepository.save(event)).thenReturn(event); // 세입할 때 event를 리턴하겠다.
		
		mockMvc.perform(post("/api/events")
				.contentType(MediaType.APPLICATION_JSON_UTF8)
				.accept(MediaTypes.HAL_JSON)
				.content(objectMapper.writeValueAsString(event)))
		.andDo(print())
		.andExpect(status().isCreated()) // 201 == isCreated
		.andExpect(jsonPath("id").exists());
	}
}

@WebMvcTest

  •  MockMvc 빈을 자동 설정 해준다. 따라서 그냥 가져와서 쓰면 됨.
  • 웹 관련 빈만 등록해 준다. (슬라이스)

MockMvc

  • 스프링 MVC 테스트 핵심 클래스
  • 웹 서버를 띄우지 않고도 스프링 MVC (DispatcherServlet)가 요청을 처리하는 과정을 확인할 수 있기 때문에 컨트롤러 테스트용으로 자주 쓰임

ObjectMapper

  • jackson 의존성을 등록하면 따로 선언없이 주입할 수 있는 친구
  • 객체를 json문자열로 보낼때 쓰인다.

Controller

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;

import java.net.URI;

import org.springframework.hateoas.MediaTypes;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping(value = "/api/events", produces = MediaTypes.HAL_JSON_UTF8_VALUE)
public class EventController {
	
	@PostMapping
	public ResponseEntity createEvent(@RequestBody Event event) {
		URI createUri = linkTo(EventController.class).slash("{id}").toUri(); 
        	// 응답정보에 생성한 이벤트의조회uri담기
		event.setId(10);
		return ResponseEntity.created(createUri).body(event);
	}
}

클래스 위에 RequestMapping을 정의함으로써 methodOn으로 굳이 uri를 찾을 필요가 없어졌다.

*참고로 linkTo 와 methodOn은 uri를 만들때 쓰이는 Hateoas 안에 있는 것인데 1.1? 이후로 없어졌으므로 주의바람

 

produces는 응답을 저 데이터 형태로 보내겠다.

 

 

Repository

import org.springframework.data.jpa.repository.JpaRepository;

public interface EventRepository extends JpaRepository<Event, Integer>{
}

HAL_JSON 형태

http://stateless.co/hal_specification.html

 

The Hypertext Application Language

HAL - Hypertext Application Language A lean hypermedia type Summary HAL is a simple format that gives a consistent and easy way to hyperlink between resources in your API. Adopting HAL will make your API explorable, and its documentation easily discoverabl

stateless.co

 

Comments