1. 목적
협업을 하면서 커밋을 기다리거나 구현한 기능에 대해 실행하면서 웹페이지로 테스트를 진행하는 것이 비효율적임을 느끼게 되어 테스트코드를 이용한 단위테스트를 필수적으로 진행해야겠다고 생각했다.
2. 학습내용
given-when-then
- given은 테스트 실행을 준비하는 단계
- when은 테스트를 진행하는 단계
- then은 테스트 결과를 검증하는 단계
JUnit 애너테이션
- @DisplayName : 테스트 이름을 명시
- @Test : 테스트 수행 메서드
- @BeforeAll : 전체테스트를 시작하기 전에 처음으로 한번만 실행한다. 데이터베이스를 연결해야 하거나 테스트 환경을 초기화할 때 사용된다. 전체 테스트 실행 주기에서 한번만 호출되어야 하기 때문에 statc으로 선언해야 한다.
- @BeforeEach : 테스트 케이스를 시작하기전에 매번 실행한다. 테스트 메서드에서 사용하는 객체를 초기화하거나 테스트에 필요한 값을 미리 넣을 때 사용한 수 있다. 각 인스턴스에 대해 메서드를 호출해야 하므로 메서드는 static이 아니어야 한다.
- @AfterAll : 전체 테스트를 마치고 종료하기 전에 한 번만 실행한다. 데이터베이스 연결을 종료할때나 공통적으로 사용한 자원을 해제할 때 사용할 수 있다. 전체 테스트 실행 주기에서 한번만 호출되어야 하므로 메서드를 static으로 선언해야 한다.
- @AfterEach : 각 테스트 케이스를 종료하기 전 매번 실행한다. 테스트 이후에 특정 데이터를 삭제해야 하는 경우 사용한다. static이 아니어야 한다.
AssertJ
Assertion.assertEquals(a+b, sum) -> assertThat(a+b).isEqualTo(sum);
3. 작성코드
package com.team.RecipeRadar.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.team.RecipeRadar.domain.recipe.domain.Recipe;
import com.team.RecipeRadar.domain.recipe.dto.AddRecipeRequest;
import com.team.RecipeRadar.domain.recipe.dto.UpdateRecipeRequest;
import com.team.RecipeRadar.domain.recipe.dao.RecipeRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
class RecipeControllerTest {
@Autowired
protected MockMvc mockMvc;
@Autowired // 직렬화, 역직렬화를 위한 클래스
protected ObjectMapper objectMapper;
@Autowired
private WebApplicationContext context;
@Autowired
RecipeRepository recipeRepository;
@BeforeEach // 테스트 실행전 실행하는 메서드
public void setMockMvc() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(context).
build();
recipeRepository.deleteAll();
}
@DisplayName("addRecipe: 레시피 추가에 성공한다.")
@Test
public void addRecipe() throws Exception {
//given
final String url = "/api/admin/recipes";
final String recipeTitle = "recipeTitle";
final String recipeContent = "recipeContent";
final String recipeServing = "recipeServing";
final String cookingTime = "cookingTime";
final String ingredientsAmount = "ingredientsAmount";
final String cookingStep = "cookingStep";
final String recipeLevel = "recipeLevel";
final AddRecipeRequest userRequest = new AddRecipeRequest(recipeTitle, recipeContent, recipeServing, cookingTime, ingredientsAmount, cookingStep, recipeLevel);
//객체 JSON으로 직렬화
final String requestBody = objectMapper.writeValueAsString(userRequest);
//when
//설정한 내용을 바탕으로 요청 전송
ResultActions result = mockMvc.perform(post(url)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(requestBody));
//then
result.andExpect(status().isCreated());
List<Recipe> recipes = recipeRepository.findAll();
assertThat(recipes.size()).isEqualTo(1); //크기가 1인지 검증
assertThat(recipes.get(0).getRecipeTitle()).isEqualTo(recipeTitle);
assertThat(recipes.get(0).getRecipeContent()).isEqualTo(recipeContent);
assertThat(recipes.get(0).getRecipeServing()).isEqualTo(recipeServing);
assertThat(recipes.get(0).getCookingTime()).isEqualTo(cookingTime);
assertThat(recipes.get(0).getIngredientsAmount()).isEqualTo(ingredientsAmount);
assertThat(recipes.get(0).getCookingStep()).isEqualTo(cookingStep);
assertThat(recipes.get(0).getRecipeLevel()).isEqualTo(recipeLevel);
}
@DisplayName("findAllRecipes: 레시피 목록 조회에 성공한다.")
@Test
public void findAllRecipes() throws Exception {
final String url = "/api/admin/recipes";
final String recipeTitle = "recipeTitle";
final String recipeContent = "recipeContent";
final String recipeServing = "recipeServing";
final String cookingTime = "cookingTime";
final String ingredientsAmount = "ingredientsAmount";
final String cookingStep = "cookingStep";
final String recipeLevel = "recipeLevel";
recipeRepository.save(Recipe.builder()
.recipeTitle(recipeTitle)
.recipeContent(recipeContent)
.recipeServing(recipeServing)
.cookingTime(cookingTime)
.ingredientsAmount(ingredientsAmount)
.cookingStep(cookingStep)
.recipeLevel(recipeLevel)
.build());
final ResultActions resultActions = mockMvc.perform(get(url)
.accept(MediaType.APPLICATION_JSON));
resultActions
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].recipeLevel").value(recipeLevel))
.andExpect(jsonPath("$[0].cookingStep").value(cookingStep))
.andExpect(jsonPath("$[0].ingredientsAmount").value(ingredientsAmount))
.andExpect(jsonPath("$[0].cookingTime").value(cookingTime))
.andExpect(jsonPath("$[0].recipeServing").value(recipeServing))
.andExpect(jsonPath("$[0].recipeContent").value(recipeContent))
.andExpect(jsonPath("$[0].recipeTitle").value(recipeTitle));
}
@DisplayName("findRecipe: 레시피 상세 조회에 성공한다.")
@Test
public void findRecipe() throws Exception {
final String url = "/api/admin/recipes/{id}";
final String recipeTitle = "recipeTitle";
final String recipeContent = "recipeContent";
final String recipeServing = "recipeServing";
final String cookingTime = "cookingTime";
final String ingredientsAmount = "ingredientsAmount";
final String cookingStep = "cookingStep";
final String recipeLevel = "recipeLevel";
Recipe savedRecipe = recipeRepository.save(Recipe.builder()
.recipeTitle(recipeTitle)
.recipeContent(recipeContent)
.recipeServing(recipeServing)
.cookingTime(cookingTime)
.ingredientsAmount(ingredientsAmount)
.cookingStep(cookingStep)
.recipeLevel(recipeLevel)
.build());
final ResultActions resultActions = mockMvc.perform(get(url, savedRecipe.getId()));
resultActions
.andExpect(status().isOk())
.andExpect(jsonPath("$.recipeLevel").value(recipeLevel))
.andExpect(jsonPath("$.cookingStep").value(cookingStep))
.andExpect(jsonPath("$.ingredientsAmount").value(ingredientsAmount))
.andExpect(jsonPath("$.cookingTime").value(cookingTime))
.andExpect(jsonPath("$.recipeServing").value(recipeServing))
.andExpect(jsonPath("$.recipeContent").value(recipeContent))
.andExpect(jsonPath("$.recipeTitle").value(recipeTitle));
}
@DisplayName("deleteRecipe: 레시피 삭제에 성공한다.")
@Test
public void deleteRecipe() throws Exception {
final String url = "/api/admin/recipes/{id}";
final String recipeTitle = "recipeTitle";
final String recipeContent = "recipeContent";
final String recipeServing = "recipeServing";
final String cookingTime = "cookingTime";
final String ingredientsAmount = "ingredientsAmount";
final String cookingStep = "cookingStep";
final String recipeLevel = "recipeLevel";
Recipe savedRecipe = recipeRepository.save(Recipe.builder()
.recipeTitle(recipeTitle)
.recipeContent(recipeContent)
.recipeServing(recipeServing)
.cookingTime(cookingTime)
.ingredientsAmount(ingredientsAmount)
.cookingStep(cookingStep)
.recipeLevel(recipeLevel)
.build());
mockMvc.perform(delete(url, savedRecipe.getId()))
.andExpect(status().isOk());
List<Recipe> recipes = recipeRepository.findAll();
assertThat(recipes).isEmpty();
}
@DisplayName("updateRecipe: 레시피 수정에 성공한다.")
@Test
public void updateRecipe() throws Exception {
final String url = "/api/admin/recipes/{id}";
final String recipeTitle = "recipeTitle";
final String recipeContent = "recipeContent";
final String recipeServing = "recipeServing";
final String cookingTime = "cookingTime";
final String ingredientsAmount = "ingredientsAmount";
final String cookingStep = "cookingStep";
final String recipeLevel = "recipeLevel";
Recipe savedRecipe = recipeRepository.save(Recipe.builder()
.recipeTitle(recipeTitle)
.recipeContent(recipeContent)
.recipeServing(recipeServing)
.cookingTime(cookingTime)
.ingredientsAmount(ingredientsAmount)
.cookingStep(cookingStep)
.recipeLevel(recipeLevel)
.build());
final String newRecipeTitle = "new recipeTitle";
final String newRecipeContent = "new recipeContent";
final String newRecipeServing = "new recipeServing";
final String newCookingTime = "new cookingTime";
final String newIngredientsAmount = "new ingredientsAmount";
final String newCookingStep = "new cookingStep";
final String newRecipeLevel = "new recipeLevel";
UpdateRecipeRequest request = new UpdateRecipeRequest(newRecipeTitle, newRecipeContent, newRecipeServing, newCookingTime, newIngredientsAmount, newCookingStep, newRecipeLevel);
ResultActions result = mockMvc.perform(put(url, savedRecipe.getId())
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(request)));
result.andExpect(status().isOk());
Recipe recipe = recipeRepository.findById(savedRecipe.getId()).get();
assertThat(recipe.getRecipeTitle()).isEqualTo(newRecipeTitle);
assertThat(recipe.getRecipeContent()).isEqualTo(newRecipeContent);
assertThat(recipe.getRecipeServing()).isEqualTo(newRecipeServing);
assertThat(recipe.getCookingTime()).isEqualTo(newCookingTime);
assertThat(recipe.getIngredientsAmount()).isEqualTo(newIngredientsAmount);
assertThat(recipe.getCookingStep()).isEqualTo(newCookingStep);
assertThat(recipe.getRecipeLevel()).isEqualTo(newRecipeLevel);
}
}
4. 마무리
service 패키지의 기능들과 예외처리관련해서 테스트가 어떻게 이루어지는지 공부해봐야겠다.