먼저 ResponseEntity<?>를 이용하여 빈값을 보내 Post가 기능을 하는지 안하는지 겉보기에만 작동하는것처럼 보이게 만들어 보겠다.
/test/~/interfaces/RestaurantControllerTest.java
package kr.co.fastcampus.eatgo.interfaces;
import kr.co.fastcampus.eatgo.application.RestaurantService;
import kr.co.fastcampus.eatgo.domain.*;
import org.junit.Test;
import org.junit.runner.RunWith;
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.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.core.StringContains.containsString;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@RunWith(SpringRunner.class) // api 요청처리 테스트를 해주기위한 스프링자체의 어노테이션
@WebMvcTest(RestaurantController.class) // 특정 컨트롤러를 테스트해준다는것을 명시하는 어노테이션
public class RestaurantControllerTest {
@Autowired
private MockMvc mvc; // MockMvc생성, 리퀘스트 요청을 받는 테스트를 할때 사용
@MockBean // 스프링에서 제공하는, 테스트를위한 의존주입 가짜객체를 설정하는 어노테이션
private RestaurantService restaurantService;
@Test
public void list() throws Exception {
List<Restaurant> restaurants = new ArrayList<>();
restaurants.add(new Restaurant(1004L, "Bob zip", "Seoul")); // 우리가 테스트해서 나와야될값을 만들어준것.
given(restaurantService.getRestaurants()).willReturn(restaurants); // 우리는 여기 내부에서 가게목록들을 반환하는것을 알고있다. 우리는 getRestaurants가 가짜로 임의의 목록을 반환하게 만들것이다.
//given(restaurantService.getRestaurants()) 누군가가 이걸 실행하면 willReturn(restaurants) 이렇게 리턴할것이다라는 테스트이다.
mvc.perform(get("/restaurants"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("\"id\":1004")))
.andExpect(content().string(containsString("\"name\":\"Bob zip\"")));
}
@Test
public void detail() throws Exception {
Restaurant restaurant1 = new Restaurant(1004L, "JOKER House", "Seoul");
restaurant1.addMenuItem(new MenuItem("Kimchi"));
Restaurant restaurant2 = new Restaurant(2020L, "Cyber Food", "Seoul");
restaurant2.addMenuItem(new MenuItem("Kimchi"));
given(restaurantService.getRestaurant(1004L)).willReturn(restaurant1);
given(restaurantService.getRestaurant(2020L)).willReturn(restaurant2);
mvc.perform(get("/restaurants/1004"))
.andExpect(status().isOk())
.andExpect(content().string(
containsString("\"id\":1004")
))
.andExpect(content().string(
containsString("\"name\":\"JOKER House\"")
))
.andExpect(content().string(
containsString("Kimchi") // 메뉴추가 테스트
));
mvc.perform(get("/restaurants/2020"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("\"id\":2020")))
.andExpect(content().string(containsString("\"name\":\"Cyber Food\"")));
}
@Test
public void create() throws Exception { // 생성 테스트
mvc.perform(post("/restaurants"))
.andExpect(status().isCreated()) // 생성확인
.andExpect(header().string("location","/restaurants/1234"))
.andExpect(content().string("{}")); // 빈값이 들어온다는것 확인할때 이렇게 사용
}
}
main/~/interfaces/RestaurantController.java
package kr.co.fastcampus.eatgo.interfaces;
import kr.co.fastcampus.eatgo.application.RestaurantService;
import kr.co.fastcampus.eatgo.domain.MenuItem;
import kr.co.fastcampus.eatgo.domain.MenuItemRepository;
import kr.co.fastcampus.eatgo.domain.Restaurant;
import kr.co.fastcampus.eatgo.domain.RestaurantRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
@RestController
public class RestaurantController {
@Autowired
private RestaurantService restarantService;
@GetMapping("/restaurants") // 가게목록 반환 api
public List<Restaurant> list(){
List<Restaurant> restaurants = restarantService.getRestaurants();
return restaurants;
}
@GetMapping("/restaurants/{id}") // 특정 가게상세 반환 api
public Restaurant detail(@PathVariable("id") Long id){
Restaurant restaurant = restarantService.getRestaurant(id); // 가게의 기본정보 + 메뉴 정보를 반환하는 기능능
return restaurant;
}
@PostMapping("/restaurants") // 가게 생성 api
public ResponseEntity<?> create() throws URISyntaxException {
URI location = new URI("/restaurants/1234");
return ResponseEntity.created(location).body("{}"); // 빈값 반환
}
}
이번에 우리가 만든 실제 객체들을 가지고 생성테스트를 진행해보겠다.
test/~/interfaces/RestaurantControllerTest.java
package kr.co.fastcampus.eatgo.interfaces;
import kr.co.fastcampus.eatgo.application.RestaurantService;
import kr.co.fastcampus.eatgo.domain.*;
import org.junit.Test;
import org.junit.runner.RunWith;
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.boot.test.mock.mockito.SpyBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.core.StringContains.containsString;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@RunWith(SpringRunner.class) // api 요청처리 테스트를 해주기위한 스프링자체의 어노테이션
@WebMvcTest(RestaurantController.class) // 특정 컨트롤러를 테스트해준다는것을 명시하는 어노테이션
public class RestaurantControllerTest {
@Autowired
private MockMvc mvc; // MockMvc생성, 리퀘스트 요청을 받는 테스트를 할때 사용
@MockBean // 스프링에서 제공하는, 테스트를위한 의존주입 가짜객체를 설정하는 어노테이션
private RestaurantService restaurantService;
@Test
public void list() throws Exception {
List<Restaurant> restaurants = new ArrayList<>();
restaurants.add(new Restaurant(1004L, "Bob zip", "Seoul")); // 우리가 테스트해서 나와야될값을 만들어준것.
given(restaurantService.getRestaurants()).willReturn(restaurants); // 우리는 여기 내부에서 가게목록들을 반환하는것을 알고있다. 우리는 getRestaurants가 가짜로 임의의 목록을 반환하게 만들것이다.
//given(restaurantService.getRestaurants()) 누군가가 이걸 실행하면 willReturn(restaurants) 이렇게 리턴할것이다라는 테스트이다.
mvc.perform(get("/restaurants"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("\"id\":1004")))
.andExpect(content().string(containsString("\"name\":\"Bob zip\"")));
}
@Test
public void detail() throws Exception {
Restaurant restaurant1 = new Restaurant(1004L, "JOKER House", "Seoul");
restaurant1.addMenuItem(new MenuItem("Kimchi"));
Restaurant restaurant2 = new Restaurant(2020L, "Cyber Food", "Seoul");
restaurant2.addMenuItem(new MenuItem("Kimchi"));
given(restaurantService.getRestaurant(1004L)).willReturn(restaurant1);
given(restaurantService.getRestaurant(2020L)).willReturn(restaurant2);
mvc.perform(get("/restaurants/1004"))
.andExpect(status().isOk())
.andExpect(content().string(
containsString("\"id\":1004")
))
.andExpect(content().string(
containsString("\"name\":\"JOKER House\"")
))
.andExpect(content().string(
containsString("Kimchi") // 메뉴추가 테스트
));
mvc.perform(get("/restaurants/2020"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("\"id\":2020")))
.andExpect(content().string(containsString("\"name\":\"Cyber Food\"")));
}
@Test
public void create() throws Exception { // 생성 테스트
//Restaurant restaurant = new Restaurant(1234L,"BeRyong", "Seoul");
// 처음에 이렇게 진행하면 테스트 에러가 날것이다 왜냐하면
// RestaurantController의 Restaurant restaurant 객체와 테스트의 Restaurant restaurant 객체가 다르다고 인식하기 때문이다.
// 그렇기때문에 이를 테스트하기위해서 아래의 any()메서드를 사용한다.
mvc.perform(post("/restaurants")
.contentType(MediaType.APPLICATION_JSON) // JSON형식이란것 명시
.content("{\"name\":\"BeRyong\",\"adderess\":\"Busan\"}"))
.andExpect(status().isCreated())// 생성확인
.andExpect(header().string("location","/restaurants/1234")) // 헤더의 location : /restaurants/1234 를 생성한다는 테스트
.andExpect(content().string("{}")); // body에 빈값이 들어온다는것 확인할때 이렇게 사용
verify(restaurantService).addRestaurant(any()); // 가게추가 서비스 확인, any()는 제대로된 객체를 무엇이든 넣기만하면 실행이 되는지 확인하는 메서드이다.
}
}
main/~/interfaces/RestaurantController.java
package kr.co.fastcampus.eatgo.interfaces;
import kr.co.fastcampus.eatgo.application.RestaurantService;
import kr.co.fastcampus.eatgo.domain.MenuItem;
import kr.co.fastcampus.eatgo.domain.MenuItemRepository;
import kr.co.fastcampus.eatgo.domain.Restaurant;
import kr.co.fastcampus.eatgo.domain.RestaurantRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
@RestController
public class RestaurantController {
@Autowired
private RestaurantService restarantService;
@GetMapping("/restaurants") // 가게목록 반환 api
public List<Restaurant> list(){
List<Restaurant> restaurants = restarantService.getRestaurants();
return restaurants;
}
@GetMapping("/restaurants/{id}") // 특정 가게상세 반환 api
public Restaurant detail(@PathVariable("id") Long id){
Restaurant restaurant = restarantService.getRestaurant(id); // 가게의 기본정보 + 메뉴 정보를 반환하는 기능능
return restaurant;
}
@PostMapping("/restaurants") // 가게 생성 api
public ResponseEntity<?> create(@RequestBody Restaurant resource) //@RequestBody 에 JSON으로 받을수 있는 Restaurant 클래스를 넣어 형식을 이용할수있다.
throws URISyntaxException {
String name = resource.getName();
String address = resource.getAddress();
Restaurant restaurant = new Restaurant(1234L, name, address);
restarantService.addRestaurant(restaurant);
URI location = new URI("/restaurants/" + restaurant.getId()); // 임시 URI 객체
return ResponseEntity.created(location).body("{}"); // 빈값 반환 // 헤더의 location : /restaurants/1234 을 생성하고 이것은 body에 빈값을 가졌다.
//post api 테스트방법 : http POST localhost:8080/restaurants name=BeRyon address=Busan
}
}
main/~/application/RestaurantService.java
package kr.co.fastcampus.eatgo.application;
import kr.co.fastcampus.eatgo.domain.MenuItem;
import kr.co.fastcampus.eatgo.domain.MenuItemRepository;
import kr.co.fastcampus.eatgo.domain.Restaurant;
import kr.co.fastcampus.eatgo.domain.RestaurantRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service // 서비스를 지정하는 어노테이션
public class RestaurantService {
@Autowired
RestaurantRepository restaurantRepository;
@Autowired
MenuItemRepository menuItemsRepository;
public RestaurantService(RestaurantRepository restaurantRepository,
MenuItemRepository menuItemRepository) {
this.restaurantRepository = restaurantRepository;
this.menuItemsRepository = menuItemRepository;
}
public List<Restaurant> getRestaurants() { //가게목록 반환
List<Restaurant> restaurants = restaurantRepository.findAll();
return restaurants;
}
public Restaurant getRestaurant(Long id){ // 특정 가게 반환
Restaurant restaurant = restaurantRepository.findById(id); // 요청되는 id에 따른 특정 가게 반환
List<MenuItem> menuItems = menuItemsRepository.findAllByRestaurantId(id); // 요청되는 id에 따른 가게 메뉴목록 반환
restaurant.setMenuItems(menuItems); // 특정가게의 메뉴목록들 추가
return restaurant;
}
public void addRestaurant(Restaurant restaurant) { // 현재는 메서드만 만든상태
}
}
main/~/domain/RestaurantRepository.java
package kr.co.fastcampus.eatgo.domain;
import jdk.internal.jimage.ImageStrings;
import java.util.ArrayList;
import java.util.List;
public class Restaurant {
private Long id;
private String name;
private String address;
private List<MenuItem> menuItems = new ArrayList<MenuItem>(); // 가게메뉴들을 담을 리스트 생성
public Restaurant() { // JSON형식을 사용할때 아무것도 없는 기본생성자가 꼭 있어야한다.
}
public Restaurant(Long id, String name, String address) {
this.id = id;
this.name = name;
this.address = address;
}
public Long getId() {
return id;
}
public String getName() { // 생성된 객체의 이름 반환
return name;
}
public String getAddress() { // 생성된 객체의 주소 반환
return address;
}
public String getInformaion() { // 생성된 객체의 정보 반환
return name + " in " + address;
}
public List<MenuItem> getMenuItems() { // 가게의 메뉴들 반환
return menuItems;
}
public void addMenuItem(MenuItem menuItem) { // 가게메뉴 리스트에 가게메뉴 추가 메서드
menuItems.add(menuItem);
}
public void setMenuItems(List<MenuItem> menuItems) {
for (MenuItem menuItem : menuItems){
addMenuItem(menuItem);
}
}
}
이 VO를 이용할때 JSON형식 반환형으로 사용하기 위해서는 꼭 아무것도 없는 기본생성자를 만들어 주어야한다.
일단 httpie를 설치하고
터미널에 http POST localhost:8080/restaurants name=BeRyon address=Busan
입력하면 아래와같이 /restaurnats/1234 가 생성되었다는 결과가 나오는것을 확인 할 수 있다.
다만 우리가 지금한것은 실제로 구현한것이 아닌 POST의 기능동작 여부만 확인한것이기때문에 아래와같이 목록을 확인하는 api를 보내면 현재는 아무것도 추가가 되지않은 상황을 볼 수있다.
다음시간에는 이부분을 실제로 저장할수있는 서비스와 레파지토리를 생성하도록 할것이다.