스프링 부트 프로젝트(레스토랑 예약 사이트 만들기)- 가게추가-1 14강 해석

Psj·2021년 12월 25일
0

#1

먼저 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("{}"); // 빈값 반환
    }

}

#2

이번에 우리가 만든 실제 객체들을 가지고 생성테스트를 진행해보겠다.

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 테스트

일단 httpie를 설치하고

터미널에 http POST localhost:8080/restaurants name=BeRyon address=Busan
입력하면 아래와같이 /restaurnats/1234 가 생성되었다는 결과가 나오는것을 확인 할 수 있다.

다만 우리가 지금한것은 실제로 구현한것이 아닌 POST의 기능동작 여부만 확인한것이기때문에 아래와같이 목록을 확인하는 api를 보내면 현재는 아무것도 추가가 되지않은 상황을 볼 수있다.

다음시간에는 이부분을 실제로 저장할수있는 서비스와 레파지토리를 생성하도록 할것이다.

profile
Software Developer

0개의 댓글