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

💡 Test

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

 

 

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

 

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

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

 

given - when - then Pattern

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

 

 

Hamcrest를 사용한 Assertion

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

 

Controller Test

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

 

Data Test

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

💡 구현

 

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]

 

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의 객체를 이용 

 

 

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으로 생성한 객체를 주입


Controller Test

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

Mockito Test

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

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

검색 태그