가짜객체는 우리가 원래 사용해야할 것과 똑같은 행동을 하는 객체를 의미한다.
아래 기존의 테스트코드를 가짜객체를 이용하여 바꾸도록하겠다.
지금까지는 아래처럼 RestaurantService클래스를 진짜로 투입하여 테스트를 진행했다면 이제는 가짜객체를 투입해서 진행하도록 하겠다.
-기존-
/test/~/interfaces/RestaurantControllerTest.java
package kr.co.fastcampus.eatgo.interfaces;
import kr.co.fastcampus.eatgo.application.RestaurantService;
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(RestaurantService.class) // service 테스트
private RestaurantService restaurantService;
@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\"")));
}
}
-변경후1-
/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.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생성, 리퀘스트 요청을 받는 테스트를 할때 사용
@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 {
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\"")));
}
}
list() 테스트가 성공하는것을 확인할수있다.
이렇게 가짜 서비스를 만드는 이유는 테스트컨트롤러가
서비스단이 실제로 어떻게 작동하는지는 관심갖지않고
순전히 컨트롤러에서 서비스를 활용한다는 사실 자체에만 집중하게 하는것이다.
-변경후2-
/test/~/application/RestaurantServiceTest.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.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생성, 리퀘스트 요청을 받는 테스트를 할때 사용
@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\"")));
}
}
detail()도 잘 테스트가 통과되는것을 확인할 수 있다.
여기 서비스테스트에서 의존하는 실제 레파지토리 객체들을 각짜객체로 바꿀것이다.
-기존-
/test/~/application/RestaurantServiceTest.java
package kr.co.fastcampus.eatgo.application;
import kr.co.fastcampus.eatgo.domain.*;
import org.junit.Before;
import org.junit.Test;
import java.util.List;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.*;
public class RestaurantServiceTest {
private RestaurantService restaurantService; // 실제 레파지토리 객체
private RestaurantRepository restaurantRepository; // 실제 레파지토리 객체
private MenuItemRepository menuItemRepository; // 실제 레파지토리 객체
@Before // 모든 테스트가 실행되기전 이 어노테이션이 붙은 테스트가 먼저 실행되고 다음 테스트가 실행된다
public void setUp(){
restaurantRepository = new RestaurantRepositoryImpl();
menuItemRepository = new MenuItemRepositoryImpl();
restaurantService = new RestaurantService(restaurantRepository, menuItemRepository);
}
@Test
public void getRestaurants(){
List<Restaurant> restaurants = restaurantService.getRestaurants();
Restaurant restaurant = restaurants.get(0);
assertThat(restaurant.getId(), is(1004L));
}
@Test
public void getRestaurant(){
Restaurant restaurant = restaurantService.getRestaurant(1004L);
assertThat(restaurant.getId(), is(1004L));
MenuItem menuItem = restaurant.getMenuItems().get(0);
assertThat(menuItem.getName(), is("Kimchi"));
}
}
-변경후-
/test/~/application/RestaurantServiceTest.java
package kr.co.fastcampus.eatgo.application;
import kr.co.fastcampus.eatgo.domain.*;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.given;
public class RestaurantServiceTest {
private RestaurantService restaurantService;
@Mock // 가짜객체 만드는 어노테이션
private RestaurantRepository restaurantRepository;
@Mock // 가짜객체 만드는 어노테이션
private MenuItemRepository menuItemRepository;
@Before // 모든 테스트가 실행되기전 이 어노테이션이 붙은 테스트가 먼저 실행되고 다음 테스트가 실행된다
public void setUp(){
MockitoAnnotations.initMocks(this); // 현재 @Mock 어노테이션이 붙어있는 객체를 초기화하는것
mockRestaurantRepository();
mockMenuItemRepository();
restaurantService = new RestaurantService(
restaurantRepository, menuItemRepository);
}
private void mockRestaurantRepository() {
List<Restaurant> restaurants = new ArrayList<>();
Restaurant restaurant = new Restaurant(1004L, "Bob zip", "Seoul");
restaurants.add(restaurant);
given(restaurantRepository.findAll()).willReturn(restaurants);
given(restaurantRepository.findById(1004L)).willReturn(restaurant);
}
private void mockMenuItemRepository() {
List<MenuItem> menuItems = new ArrayList<>();
menuItems.add(new MenuItem("Kimchi"));
given(menuItemRepository.findAllByRestaurantId(1004L)).willReturn(menuItems);
}
@Test
public void getRestaurants(){
List<Restaurant> restaurants = restaurantService.getRestaurants();
Restaurant restaurant = restaurants.get(0);
assertThat(restaurant.getId(), is(1004L));
}
@Test
public void getRestaurant(){
Restaurant restaurant = restaurantService.getRestaurant(1004L);
assertThat(restaurant.getId(), is(1004L));
MenuItem menuItem = restaurant.getMenuItems().get(0);
assertThat(menuItem.getName(), is("Kimchi"));
}
}
전체 테스트 성공을 확인할 수 있다.