우주먼지
article thumbnail
Published 2022. 11. 8. 08:50
Testing (Mockito) Framework/Spring

1. 💡 Test

JUnit은 표준 테스트 프레임워크이다

 

 

  • 기능 테스트
    • 테스트의 범위 중 제일 큰 테스트, 어플리케이션 전체에 걸친 테스트
  • 통합 테스트
    • 테스트 주체가 어플리케이션 제작 개발팀 or 개발자 단위 테스트, 클라이언트 툴 없음
  • 슬라이스 테스트
    • 어플리케이션을 특정 계층으로 나눠서 테스트
  • 단위 테스트
    • 어플리케이션의 핵심 비즈니스 로직 메소드의 독립적인 테스트

 

1.1. 단위 테스트의 F.I.R.S.T 원칙

  • Fast
  • Independent
  • Repeatable
  • Self-validating
  • Timely

 

1.2. given - when - then Pattern

  • given
    • 테스트에 필요한 전제조건 포함
  • when
    • 테스트 동작 지정
  • then
    • 테스트 결과 검증, 값 비교 (Assertion)

 

 

1.3. Hamcrest를 사용한 Assertion

  • Assertion을 위한 Matcher를 자연스러운 문장으로 이어지도록 하며, 가독성 향상
  • 테스트 실패 시 손쉬운 원인 파악 가능, 다양한 Matcher 제공

 

1.4. Controller Test

  • MockMvc, Gson @Autowired 주입
  • 테스트 대상 핸들러 메소드 호출 throws Exception
  • Dto 호출, 객체 생성, 데이터 삽입
  • gson.toJson() 데이터 변환
  • ResultAction 타입의 mockMvc.preform을 이용한 컨트롤러 정보 입력
    post(), accept(), contentType(), content()를 이용해 requestbody 설정
  • MvcResult 타입의 actions.andExcept("$.data.멤버"), .andRetuen()을 이용해 데이터 검증

 

1.5. Data Test

  • @DataJpaTest
  • Repo Autowired
  • Entity 객체 생성 -> 데이터 set
  • save
  • Assertion

2. 💡 구현

 

2.1. Packages

  • import static org.assertj.core.api.Assertions.*; [assertj]
  • import static org.junit.jupiter.api.Assertions.*; [junit]
  • import static org.junit.jupiter.api.Assumptions.*; [Assumptions]
  • import static org.hamcrest.MatcherAssert.*; [Hamcrest]
  • import static org.hamcrest.Matchers.*; [Hamcrest Matcher]
  • import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; [Controller 테스트]
  • import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; [Controller 테스트]
  • import org.mockito.Mockito; [Mockito]
  • import static org.mockito.BDDMockito.given; [Mockito_given]

 

2.2. Class, Method

  • Junit
    • assertEquals(a,b) - *값비교*
    • assertNotNull(대상, 실패시 메시지) - *Null 여부 체크*
    • assertThrows(Exception.class, () -> 테스트대상 메소드) -  *예외 발생 테스트*assertDoesNotThrow(() -> Do) - *예외 발생 X 테스트*

 

  • AsserJ

 

  • Assumption
    • assumeTrue() - 파라미터의 값이 true이면, 아래 로직 실행

 

  • Hamcrest
    • asserThat(a, is(equalTo(b))) - 비교
    • asserThat(a, is(notNullValue())) - Null 검증
    • asserThat(대상.class, is(예상Exception.class)) - 예외 검증

 

  • URI
    • UriComponentBuilder.newInstance().path().buildAndExpand().toUri - Build Request URI

 

  • ResultActions - 기대 HTTP Status, Content 검증
    • mockMvc.perform(get & post 등등)

 

  • MvcResult - Response Body의 HTTP Status, 프로퍼티 검증
    • ResultActions의 객체를 이용 

 

 

2.3. Annotations

@BeforeEach - *init()* 사용, 테스트 실행 전 전처리
@BeforeAll -  *initAll()* 사용, 테스트 케이스가 실행되기전 1번만 실행
@DisplayName - 테스트의 이름 지정
@SpringBootTest - Spring Boot 기반 Application Context 생성
@AutoConfigureMockMvc - Controller를 위한 앱의 자동 구성 작업, MockMvc를 이용하려면 필수로 추가해야함
@DataJpaTest - @Transactional을 포함하고 있어서, 하나의 테스트케이스 종료시 저장된 데이터 RollBack
@MockBean - Application Context에 있던 Bean을 Mockito Mock 객체를 생성 & 주입
@ExtendWith - Spring을 사용하지않고 Junit에서 Mockito의 기능을 사용하기 위해 추가
@Mock - 해당 필드의 객체를 Mock 객체로 생성
@InjectMocks - @InjectMocks를 설정한 필드에 @Mock으로 생성한 객체를 주입


2.4. Controller Test

<java />
package com.solo.soloProject.test; import com.google.gson.Gson; import com.jayway.jsonpath.JsonPath; import com.solo.soloProject.todo.entity.Todo; import com.solo.soloProject.todo.repository.TodoRepository; 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.MvcResult; import org.springframework.test.web.servlet.ResultActions; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.util.UriComponentsBuilder; import java.net.URI; import java.util.List; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.MatcherAssert.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 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.*; @SpringBootTest @AutoConfigureMockMvc public class ControllerTest { @Autowired private MockMvc mockMvc; @Autowired private Gson gson; @Autowired private TodoRepository todoRepository; /* Post Todo Test */ @Test void postTodoTest() throws Exception { //i given RequestBody -> Json 변환 TodoDto.Post post = new TodoDto.Post("abc",1, false); String content = gson.toJson(post); /* i when * Controller의 핸들러 메소드에 요청을 전송하기 위해서 perform()를 호출해야 하며, * perform() 내부에 Controller 호출을 위한 세부 정보 포함 * * MockMvcRequestBuilders 클래스를 이용해 빌더패턴으로 HTTP Request 정보 입력 = perform * post() = HTTP Method + Request URL 설정 * accpet() = 클라이언트에서 응답 받을 데이터 타입 설정 * contentType() = 서버에서 처리 가능한 데이터 타입 지정 * content() = Request Body 데이터 지정 */ ResultActions actions = mockMvc.perform( post("/v1/todos") .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) .content(content)); /* i then * when의 perform()은 ResultActions 타입의 객체를 리턴 * 이 ResultActions 객체를 이용해 테스트로 전송한 Request에 대한 검증 수행 * 첫 andExpect()에서 Matcher를 이용해 예상되는 기대 값 검증 * status().isCreated() = Response Status가 201인지 검증 * * header().string("", is(startsWith("URI")))에 대한 설명 * HTTP Header에 추가된 Localtion의 문자열 값이 "/v1/todos"로 시작하는지 검증 */ actions.andExpect(status().isCreated()) .andExpect(header().string("Location", is(startsWith("/v1/todos")))); } /* Patch Todo Test */ @Test void patchMemberTest() throws Exception { Todo member = new Todo("abc", 1, false); Todo savedTodo = todoRepository.save(member); long todoId = savedTodo.getTodoId(); TodoDto.Patch patch = new TodoDto.Patch(1, "aaa", 2, true); String patchContent = gson.toJson(patch); URI patchUri = UriComponentsBuilder.newInstance().path("/v1/todos/{todo-id}").buildAndExpand(todoId).toUri(); ResultActions actions = mockMvc.perform(patch(patchUri) .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) .content(patchContent)); actions.andExpect(status().isOk()) .andExpect(jsonPath("$.data.title").value(patch.getTitle())) .andExpect(jsonPath("$.data.order").value(patch.getOrder())) .andExpect(jsonPath("$.data.completed").value(patch.isCompleted())); } /* Get Todo Test */ @Test void getMemberTest() throws Exception { TodoDto.Post post = new TodoDto.Post("abc", 1, false); String postContent = gson.toJson(post); ResultActions postActions = mockMvc.perform(post("/v1/todos") .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) .content(postContent)); long memberId; //i postMember()의 response에 전달되는 Location Header를 가져옴 = "/v1/todos/1 String location = postActions.andReturn().getResponse().getHeader("Location"); //i 위에서 얻은 Location Header 값 중 memberId에 해당하는 부분만 추출 memberId = Long.parseLong(location.substring(location.lastIndexOf("/")+1)); //i Build Request URI URI getUri = UriComponentsBuilder.newInstance().path("/v11/members/{member-id}").buildAndExpand(memberId).toUri(); mockMvc.perform( //i 기대 HTTP Status가 200 인지 검증 get(getUri).accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) //i Response Body의 프로퍼티가 응답으로 받은 프로퍼티의 값과 일지하는지 검증 .andExpect(jsonPath("$.data.title").value(post.getTitle())) .andExpect(jsonPath("$.data.order").value(post.getOrder())); } /* Get Todos Test */ @Test @DisplayName("Get Todos Test") public void getTodosTest() throws Exception { Todo todo1 = new Todo("abc", 1, false); Todo todo2 = new Todo("aaa", 2, false); Todo save1 = todoRepository.save(todo1); Todo save2 = todoRepository.save(todo2); long todoId1 = save1.getTodoId(); long todoId2 = save2.getTodoId(); String page = "1"; String size = "5"; MultiValueMap<String, String> queryparams = new LinkedMultiValueMap<>(); queryparams.add("page", page); queryparams.add("size", size); URI getUri = UriComponentsBuilder.newInstance().path("/v1/todos").build().toUri(); ResultActions actions = mockMvc.perform(get(getUri) .params(queryparams) .accept(MediaType.APPLICATION_JSON)); MvcResult result = actions.andExpect(status().isOk()) .andExpect(jsonPath("$.data").isArray()) .andReturn(); List list = JsonPath.parse(result.getResponse().getContentAsString()).read("$.data"); assertThat(list.size(), is(2)); } /* Delete Todo Test */ @Test @DisplayName("Delete Todo Test") public void deleteTodoTest() throws Exception { Todo todo1 = new Todo("abc",1,false); Todo saveTodo = todoRepository.save(todo1); long todoId = saveTodo.getTodoId(); URI uri = UriComponentsBuilder.newInstance().path("/v1/todos/{todo-id}").buildAndExpand(todoId).toUri(); mockMvc.perform(delete(uri)) .andExpect(status().isNoContent()); } }

2.5. Mockito Test

<java />
package com.solo.soloProject.test; import com.google.gson.Gson; import com.solo.soloProject.todo.entity.Todo; import com.solo.soloProject.todo.mapper.TodoMapper; import com.solo.soloProject.todo.service.TodoService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.Mockito; 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.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.startsWith; import static org.mockito.BDDMockito.given; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest @AutoConfigureMockMvc public class MockitoTest { @Autowired private MockMvc mockMvc; @Autowired private Gson gson; @MockBean private TodoService todoService; @Autowired private TodoMapper mapper; /* Use Mockito Post Todo Test */ @Test @DisplayName("Mockito를 이용한 Post Todo Test") public void mockPostTest() throws Exception { TodoDto.Post post = new TodoDto.Post("abc", 1, false); Todo todo = mapper.TodoPostToTodo(post); todo.setTodoId(1L); given(todoService.createTodo(Mockito.any(Todo.class))).willReturn(todo); String content = gson.toJson(post); ResultActions actions = mockMvc.perform(post("/v1/todos") .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) .content(content)); actions.andExpect(status().isCreated()) .andExpect(header().string("Location", is(startsWith("/v1/todos")))); } }

'Framework > Spring' 카테고리의 다른 글

Application Deployment & Build  (0) 2022.11.15
API Documentation (Spring RestDocs)  (0) 2022.11.12
분산 트랜잭션  (0) 2022.11.08
선언형 트랜잭션  (0) 2022.11.04
JPA & Hibernate (Java Persistence API)  (0) 2022.11.01
profile

우주먼지

@o귤o

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

검색 태그