스프링 부트 프로젝트(레스토랑 예약 사이트 만들기)-레이어 분리-1 11강 해석

Psj·2021년 12월 20일
0

#1 특정가게의 메뉴표시

/test/~/interfaces/RestaurantControllerTest.java

package kr.co.fastcampus.eatgo.interfaces;

import kr.co.fastcampus.eatgo.domain.RestaurantRepository;
import kr.co.fastcampus.eatgo.domain.RestaurantRepositoryImpl;
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.SpyBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.hamcrest.core.StringContains.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class) // api 요청처리 테스트를 해주기위한 스프링자체의 어노테이션
@WebMvcTest(RestaurantController.class) // 특정 컨트롤러를 테스트해준다는것을 명시하는 어노테이션
public class RestaurantControllerTest {

    @Autowired
    private MockMvc mvc; // MockMvc생성

    @SpyBean(RestaurantRepositoryImpl.class) // 아래의 인터페이스가 실질적으로 어떤 클래스를 구현하는지 명시해줘야한다.
    private RestaurantRepository restaurantRepository;

    @Test
    public void list() throws Exception {
        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 {
        mvc.perform(get("/restaurants/1004"))
                .andExpect(status().isOk())
                .andExpect(content().string(containsString("\"id\":1004")))
                .andExpect(content().string(containsString("\"name\":\"Bob zip\"")))
                .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\"")));
    }
}

/main/~/interfaces/RestaurantController.java

package kr.co.fastcampus.eatgo.interfaces;

import kr.co.fastcampus.eatgo.domain.MenuItem;
import kr.co.fastcampus.eatgo.domain.Restaurant;
import kr.co.fastcampus.eatgo.domain.RestaurantRepository;
import kr.co.fastcampus.eatgo.domain.RestaurantRepositoryImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class RestaurantController {

    @Autowired // 객체를 따로 생성안해도 @Component가 붙어있는 클래스를 찾아 자동으로 의존주입을하게된다.
    private RestaurantRepository repository;

    @GetMapping("/restaurants") // 가게목록 반환 api
    public List<Restaurant> list(){

        List<Restaurant> restaurants = repository.findAll();

        return restaurants;
    }

    @GetMapping("/restaurants/{id}") // 특정 가게상세 반환 api
    public Restaurant detail(@PathVariable("id") Long id){

        Restaurant restaurant = repository.findById(id);

        restaurant.addMenuItem(new MenuItem("Kimchi")); // Kimchi 메뉴추가

        return restaurant;
    }

}

/main/~/domain/Restaurant.java

package kr.co.fastcampus.eatgo.domain;

import jdk.internal.jimage.ImageStrings;

import java.util.ArrayList;
import java.util.List;

public class Restaurant {

    private final Long id;
    private final String name;
    private final String address;
    private List<MenuItem> menuItems = new ArrayList<MenuItem>(); // 가게메뉴들을 담을 리스트 생성


    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);
    }
}

/main/~/domain/MenuItem.java 생성

package kr.co.fastcampus.eatgo.domain;

public class MenuItem {

    private final String name;

    public MenuItem(String name) { // 생성자
        this.name = name;
    }

    public String getName(){
        return name;
    }
}

정상작동하는것을 확인할 수 있다.

현재 컨트롤러 로직을 보면 특정 가게의 정보를 얻어온 다음 다시 가게의 메뉴를 추가하는것을 확인 할 수 있다.

이제 이 대신 더 효율적으로 관리하기위해 우리가 메뉴 아이템들을 전체적으로 관리할 수 있는 레파지토리를 하나 더 생성할것이다.

#2 특정 가게에 대한 메뉴 체계적 관리

/test/~/interfaces/RestaurantControllerTest.java

package kr.co.fastcampus.eatgo.interfaces;

import kr.co.fastcampus.eatgo.domain.MenuItemRepository;
import kr.co.fastcampus.eatgo.domain.MenuItemRepositoryImpl;
import kr.co.fastcampus.eatgo.domain.RestaurantRepository;
import kr.co.fastcampus.eatgo.domain.RestaurantRepositoryImpl;
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.SpyBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.hamcrest.core.StringContains.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class) // api 요청처리 테스트를 해주기위한 스프링자체의 어노테이션
@WebMvcTest(RestaurantController.class) // 특정 컨트롤러를 테스트해준다는것을 명시하는 어노테이션
public class RestaurantControllerTest {

    @Autowired
    private MockMvc mvc; // MockMvc생성

    @SpyBean(RestaurantRepositoryImpl.class) // 아래의 인터페이스가 실질적으로 어떤 클래스를 구현하는지 명시해줘야한다.
    private RestaurantRepository restaurantRepository;

    @SpyBean(MenuItemRepositoryImpl.class)
    private MenuItemRepository menuItemRepository;

    @Test
    public void list() throws Exception {
        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 {
        mvc.perform(get("/restaurants/1004"))
                .andExpect(status().isOk())
                .andExpect(content().string(containsString("\"id\":1004")))
                .andExpect(content().string(containsString("\"name\":\"Bob zip\"")))
                .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 detail() throws Exception {
mvc.perform(get("/restaurants/1004"))
    .andExpect(status().isOk())
    .andExpect(content().string(containsString("\"id\":1004")))
    .andExpect(content().string(containsString("\"name\":\"Bob zip\"")))
    .andExpect(content().string(
            containsString(("Kimchi")) // 메뉴추가 테스트
));

이렇게 "/restaurants/1004"요청시 이 부분이 정상적인 테스트가 되는지 확인할 것이다.

/main/~/interfaces/RestaurantController.java

package kr.co.fastcampus.eatgo.interfaces;

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.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class RestaurantController {

    @Autowired // 객체를 따로 생성안해도 @Component가 붙어있는 클래스를 찾아 자동으로 의존주입을하게된다.
    private RestaurantRepository restaurantRepository;

    @Autowired
    private MenuItemRepository menuItemsRepository; // MenuItemRepository 의존주입

    @GetMapping("/restaurants") // 가게목록 반환 api
    public List<Restaurant> list(){

        List<Restaurant> restaurants = restaurantRepository.findAll();

        return restaurants;
    }

//-----------------------우리가 지금 테스트할 곳--------------------------------------
    @GetMapping("/restaurants/{id}") // 특정 가게상세 반환 api
    public Restaurant detail(@PathVariable("id") Long id){

        Restaurant restaurant = restaurantRepository.findById(id);

        List<MenuItem> menuItems = menuItemsRepository.findAllByRestaurantId(id); // 요청되는 id에 따른 가게 메뉴리스트 생성
        restaurant.setMenuItems(menuItems);  // 특정가게의 메뉴목록들 추가

        return restaurant;
    }
//-----------------------------------------------------------------------------------
}

@Autowired
private RestaurantRepository restaurantRepository;

이 라인에서 의존주입으로 자동객체 생성
아래에서 객체생성시 생성자 확인

/main/~/domain/RestaurantRepositoryImpl.java

package kr.co.fastcampus.eatgo.domain;

import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component // 스프링이 관리할수 있게 @Component를 붙인다. 그럼 Autowired가 사용되는곳에서 이 @Component가 붙어있는곳을찾아 자동으로 의존성주입을 하게된다.
public class RestaurantRepositoryImpl implements RestaurantRepository {

    private List<Restaurant> restaurants = new ArrayList<>(); // 가게목록 담을 리스트

    public RestaurantRepositoryImpl(){ // 생성자를 이용해 가게 객체 추가
        restaurants.add(new Restaurant(1004L,"Bob zip", "Seoul"));
        restaurants.add(new Restaurant(2020L, "Cyber Food", "Seoul"));
    }

    @Override
    public List<Restaurant> findAll() { // 가게목록 반환
        return restaurants;
    }

    @Override
    public Restaurant findById(Long id) { // 특정가게 상세 반환

        return restaurants.stream()
                .filter(r -> r.getId().equals(id))
                .findFirst()
                .orElse(null);
    }
}

private List restaurants = new ArrayList<>();

public RestaurantRepositoryImpl(){
restaurants.add(new Restaurant(1004L,"Bob zip", "Seoul"));
restaurants.add(new Restaurant(2020L, "Cyber Food", "Seoul"));
}

생성자에서 가게 각각의 객체를 가게리스트에 추가해주는것을 확인할수있다.

@Override
public Restaurant findById(Long id) { // 특정가게 상세 반환

   return restaurants.stream()
           .filter(r -> r.getId().equals(id))
           .findFirst()
           .orElse(null);

}

그리고 findById() 메서드에서 요청받은 id값에 맞는 객체를 반환해주는것을 확인할 수 있다.


/main/~/interfaces/RestaurantController.java

package kr.co.fastcampus.eatgo.interfaces;

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.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class RestaurantController {

    @Autowired // 객체를 따로 생성안해도 @Component가 붙어있는 클래스를 찾아 자동으로 의존주입을하게된다.
    private RestaurantRepository restaurantRepository;

    @Autowired
    private MenuItemRepository menuItemsRepository; // MenuItemRepository 의존주입

    @GetMapping("/restaurants") // 가게목록 반환 api
    public List<Restaurant> list(){

        List<Restaurant> restaurants = restaurantRepository.findAll();

        return restaurants;
    }

//-----------------------우리가 지금 테스트할 곳--------------------------------------
    @GetMapping("/restaurants/{id}") // 특정 가게상세 반환 api
    public Restaurant detail(@PathVariable("id") Long id){

        Restaurant restaurant = restaurantRepository.findById(id);

        List<MenuItem> menuItems = menuItemsRepository.findAllByRestaurantId(id); // 요청되는 id에 따른 가게 메뉴리스트 생성
        restaurant.setMenuItems(menuItems);  // 특정가게의 메뉴목록들 추가

        return restaurant;
    }
//-----------------------------------------------------------------------------------
}

@Autowired
private MenuItemRepository menuItemsRepository;

이 라인에서 의존주입으로 자동객체 생성
아래에서 객체생성시 생성자 확인

/main/~/domain/MenuItemRepositoryImpl.java


package kr.co.fastcampus.eatgo.domain;

import java.awt.*;
import java.util.ArrayList;
import java.util.List;

@Component
public class MenuItemRepositoryImpl implements MenuItemRepository{

    List<MenuItem> menuItems = new ArrayList<>(); // 메뉴들 담을 리스트 생성

    MenuItemRepositoryImpl(){
        menuItems.add(new MenuItem("Kimchi")); // 메뉴리스트에 메뉴추가
    }

    @Override
    public List<MenuItem> findAllByRestaurantId(Long restaurantId) {

        return menuItems; // 현재는 임시로 가게 id에 따른 메뉴리스트반환이 아닌 무조건적인 추가된 메뉴리스트 반환으로 설정해놨음
    }
}

/main/~/domain/MenuItem.java

package kr.co.fastcampus.eatgo.domain;

public class MenuItem {

    private final String name;

    public MenuItem(String name) { // 생성자
        this.name = name;
    }

    public String getName(){
        return name;
    }
}

/main/~/interfaces/RestaurantController.java

package kr.co.fastcampus.eatgo.interfaces;

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.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class RestaurantController {

    @Autowired // 객체를 따로 생성안해도 @Component가 붙어있는 클래스를 찾아 자동으로 의존주입을하게된다.
    private RestaurantRepository restaurantRepository;

    @Autowired
    private MenuItemRepository menuItemsRepository; // MenuItemRepository 의존주입

    @GetMapping("/restaurants") // 가게목록 반환 api
    public List<Restaurant> list(){

        List<Restaurant> restaurants = restaurantRepository.findAll();

        return restaurants;
    }

//-----------------------우리가 지금 테스트할 곳--------------------------------------
    @GetMapping("/restaurants/{id}") // 특정 가게상세 반환 api
    public Restaurant detail(@PathVariable("id") Long id){

        Restaurant restaurant = restaurantRepository.findById(id);

        List<MenuItem> menuItems = menuItemsRepository.findAllByRestaurantId(id); // 요청되는 id에 따른 가게 메뉴리스트 생성
        restaurant.setMenuItems(menuItems);  // 특정가게의 메뉴목록들 추가

        return restaurant;
    }
//-----------------------------------------------------------------------------------
}

위 과정들에서 menuItems.add(new MenuItem("Kimchi"));로 menuItems 가 반환되어 있는 상태인것을 확인할 수 있다.
이 리스트가 아래의 코드에서 setMenuItems(menuItems)파라미터에 들어간다.

restaurant.setMenuItems(menuItems);

/main/~/domain/Restaurant.java

package kr.co.fastcampus.eatgo.domain;

import jdk.internal.jimage.ImageStrings;

import java.util.ArrayList;
import java.util.List;

public class Restaurant {

    private final Long id;
    private final String name;
    private final String address;
    private List<MenuItem> menuItems = new ArrayList<MenuItem>(); // 가게메뉴들을 담을 리스트 생성


    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) { // 2. 아래 메서드에서 하나씩 뽑힌 객체가 menuItems에 추가 된다.
        menuItems.add(menuItem);
    }

    public void setMenuItems(List<MenuItem> menuItems) { // 1. 리스트에 담긴 메뉴를 for문으로 뽑아 addMenuItme(menuItem)에 객체 하나씩으로 넣는다.
        for (MenuItem menuItem : menuItems){
            addMenuItem(menuItem);
        }
    }
}

그리하여 "http://localhost:8080/restaurants/1004" 의 요청결과는 아래와같이 나오게된다.

{"id":1004,"name":"Bob zip","address":"Seoul","menuItems":[{"name":"Kimchi"}],"informaion":"Bob zip in Seoul"}

profile
Software Developer

0개의 댓글