관리 메뉴

제뉴어리의 모든것

[Section3] [Spring MVC] 테스팅(Testing) - 2 (API 계층 테스트) 본문

코드스테이츠/정리 블로깅

[Section3] [Spring MVC] 테스팅(Testing) - 2 (API 계층 테스트)

제뉴어리맨 2022. 9. 17. 01:58

아래의 내용들을 원활히 이해하기 위해선 

https://januaryman.tistory.com/458?category=959308 

 

테스트시 사용되는 각종 애노테이션

사전 지식 Mockito는 Mock 객체를 생성하고, 해당 Mock 객체가 진짜처럼 동작하게 하는 기능을 하는 Mocking framework(또는 라이브러리)이다. @SpringBootTest 클래스 레벨에 붙이는 애노테이션. @SpringBootAppl..

januaryman.tistory.com

위에 포스트에 있는 기본적인 애노테이션을 알아두는것이 좋다.

 

 

build.gradle

plugins {
	id 'org.springframework.boot' version '2.7.1'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

group = 'com.codestates'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-validation'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.h2database:h2'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	implementation 'org.mapstruct:mapstruct:1.5.1.Final'
	annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.1.Final'
	implementation 'org.springframework.boot:spring-boot-starter-mail'

	implementation 'com.google.code.gson:gson'
}

tasks.named('test') {
	useJUnitPlatform()
}

 

API 계층 테스트

API 계층 테스트의 대상은 주로 클라이언트의 요청을 받아들이는 Controller 이다.

 

  • API 계층 테스트 기본 구조
import com.google.gson.Gson;
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 static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest //(1)
@AutoConfigureMockMvc //(2)
class MemberControllerTest {
    @Autowired
    private MockMvc mockMvc; //(3)

    @Autowired
    private Gson gson; //(4)

    @Test
    void postMemberTest() throws Exception {
        // given  
        MemberDto.Post post = new MemberDto.Post("hgd@gmail.com", //(5)
                                                        "홍길동",
                                                    "010-1234-5678");
        String content = gson.toJson(post);  //(6)

        // when
        ResultActions actions =
                mockMvc.perform(                 //(7)     
	                                post("/v11/members")  
                                        .accept(MediaType.APPLICATION_JSON) 
                                        .contentType(MediaType.APPLICATION_JSON) 
                                        .content(content)   
                                );

        // then
        MvcResult result = actions  //(8)
                                .andExpect(status().isCreated()) 
                                .andReturn();  

        // System.out.println(result.getResponse().getContentAsString()); // (9)
    }
}

(1) @SpringBootTest : 위에 포스트에서 잘 나와있지만, 앱 자체를 띄우는 환경의 테스트를 하겠다는것이다.

(2) @AutoConfigureMockMvc : 의존성 주입을 받고 있는 MockMvc에 대한 설정을 자동으로 해주겠다는 것이다.

 

사실 정말 Controller단만의 테스트를 하고 싶다면

@SpringBootTest 애노테이션 대신에

@WebMvcTest 애노테이션을 쓰는것이 맞다고 생각한다.

왜냐면 @SpringBootTest 애노테이션을 사용하면 테스트 하려는 Controller 뿐만 아니라, Spring 기본 빈들과 내가 정의한 빈들 또한 모두 등록이 되기 때문이다.

하지만

@WebMvcTest(MemberController.class) 이런식으로 매개변수에 넣은 테스트할 Controller만 빈으로 등록할 수 있다.

 

 

(3) : Controller 계층으로 request를 보낼 수 있게 해주는 MockMvc 객체를 선언하고 주입받았다.

@AutoConfigureMockMvc 애노테이션의 기능으로 MockMvc는 빈으로 등록되고 내부 설정까지 자동으로 이루어진다.

그러므로 @Autowired가 가능한것이다.

 

(4) : 객체를 Json 구조의 String으로 변환 시켜주는 라이브러리이다.

#의문 : Gson 클래스는 어디에서 빈등록을 해주는것일까?

 

(5) : 테스트를 위해 RequestBody에 담을 DTO 객체의 선언

 

(6) : DTO 객체를 JSON 구조의 String으로 변환

 

(7) : MockMvc 객체의 perform() 메소드를 이용하여 post 메소드를 보내는 내용이다.

perform() 메소드는 결국 RequestBuilder 타입의 인자를 받는 메소드이다.

그 안에 있는 patch, accept 등의 메소드는 RequestBuilder를 리턴하는 static 메소드인 것이다.

.accept, .contentType은 헤더 내용 추가임.

 

(8) : (7) 에서 보낸 Request에 대한 Response 내용을 담은 ResultAction 객체를 가지고 Response 내용에 대해서 Expect를 하여 Controller 핸들러 메소드의 결과를 검증하는 부분이다.

 

(9) : 해당 코드처럼 Controller가 반환한 Response에 대해서 그대로 콘솔에 찍어볼 수 있다.

그러나, 한글 내용이 깨져서 나올 수가 있다.

그럴경우, 아래의 내용을 application.yml 파일 (application.properties) 최상위 루트에 넣어주면 된다.

server:                           // 해당 라인 부터 제일 밑에 라인까지 한글 깨짐 방지
  servlet:
    encoding:
      force-response: true

 

  • 예시
spring:
  h2:
    console:
      enabled: true
      path: /h2
  datasource:
    url: jdbc:h2:mem:test
  jpa:
    hibernate:
      ddl-auto: create  # (1) 스키마 자동 생성
    show-sql: true      # (2) SQL 쿼리 출력
    properties:
      hibernate:
        format_sql: true  # (3) SQL pretty print
  sql:
    init:
      data-locations: classpath*:db/h2/data.sql
logging:
  level:
    org:
      springframework:
        orm:
          jpa: DEBUG
server:                           // 해당 라인 부터 제일 밑에 라인까지 한글 깨짐 방지
  servlet:
    encoding:
      force-response: true

 

+ Gson 사용 하려면 

build.gradle에 의존성을 아래와 같이 추가해줘야함.

dependencies {...}implementation 'com.google.code.gson:gson'

 

+ perform() 메소드 안에 들어가는 post, get 같은 메소드를 사용하려면

org.springframework.test.web.servlet.request.MockMvcRequestBuilder 패키지를 import 시켜줘야한다.

 

  • 예시
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;

										:
                                        :

 

 

Controller의 Response 내용 추가 검증

        MvcResult result = actions
                                .andExpect(status().isCreated())
                                .andExpect(jsonPath("$.data.email").value(post.getEmail()))  // (1)
                                .andExpect(jsonPath("$.data.name").value(post.getName()))   // (2)
                                .andExpect(jsonPath("$.data.phone").value(post.getPhone())) // (3)
                                .andReturn();

andExpect메소드 내에 jsonPath() 메소드를 이용하여 

data 내용을 검증 할 수 있다.

 

참고로 현재 Controller로 부터 넘어 온 Json데이터는 아래의 구조와같다.

{"data":{"memberId":1,"email":null,"name":"준","phone":null,"memberStatus":"활동중","stamp":0}}

 

$.data.email 의 해석 : $은 json 객체 자체를 의미하며, 해당 객체에 있는 필드에 접근하려면 . 을 찍어 접근한다.

그리고 data 안에 있는 email 필드를 접근하려면, 또 . 을 찍어서 email에 접근한다.

이렇게 json 객체에 접근하는 방법에는

위와 같은 

  • Dot 표현법
  • Bracket 표현 법이 있다.

데이터에 접근하는 자세한 방법은 아래의 블로그를 참조하자.

 

+ jsonPath

jsonPath는 MockMvcResultMatchers 클래스에 존재하는 정적 메소드로써

Json 객체의 데이터를 탐색할 수 있게 해준다.

 

참조 : https://joojimin.tistory.com/52

 

JsonPath란?

소스코드: https://github.com/json-path/JsonPath GitHub - json-path/JsonPath: Java JsonPath implementation Java JsonPath implementation. Contribute to json-path/JsonPath development by creating an ac..

joojimin.tistory.com

 


결론 :

위에 테스트는 API 테스트라고 했지만, 

Controller만을 테스트 하는 상황이 아니다.

@SpringBootTest 를 사용하여 앱내의 모든 빈들을 등록한 상태이며, 

서버로 Request를 보내면 Service 계층, Repository까지 모두 거친다.

그러므로, @MockBean 등을 이용하여 Service 계층과의 연결을 끊어줘야한다.

추후 [Section3] [Spring MVC] 테스팅(Testing)  게시물에서 보도록 하자.

 

 


위에 내용들을 이용한 Git Hub 프로젝트 : https://github.com/JanuaryKim/be-homework-testing-slice