💡 MockMvc - 스프링 프레임워크에서 제공하는 웹 애플리케이션 테스트용 라이브러리를 의미합니다. 이를 사용하면 웹 애플리케이션의 다양한 컴포넌트를 테스트할 수 있습니다.
- MockMvc를 사용하면 HTTP 요청을 작성하고 컨트롤러의 응답을 검증할 수 있습니다. 이를 통해 통합 테스트를 실행하지 않고도 컨트롤러의 동작을 확인할 수 있습니다.
1. MockMvc를 이용한 테스트 목적
💡 MockMvc를 이용한 테스트 목적 - MockMvc를 이용하여 컨트롤러의 동작을 테스트하는 데 사용됩니다. - 컨트롤러의 엔드포인트를 호출하여 HTTP 클라이언트의 요청을 모방하고 적절한 응답을 확인하기 위해 테스트를 수행합니다. - 이러한 테스트 과정을 통해 애플리케이션의 서비스 로직이나 API 엔드포인트가 의도한 대로 동작하는지 확인하고, 버그를 발견하고 수정하는 데 도움을 주는 것입니다.
2. MockMvc를 이용한 Controller내의 흐름
💡 MockMvc를 이용한 Controller내의 흐름 1. TestCase → MockMvc - TestCase 내에서 MockMvc 객체를 생성합니다. 이 객체는 테스트할 컨트롤러와 상호작용을 하는 데 사용이 됩니다.
2. MockMvc → TestDispatcher Servlet - MockMvc를 사용하여 원하는 엔드포인트에 요청을 보냅니다. 또한 해당 요청에 필요한 파라미터, 헤더 또는 쿠키 등을 설정합니다. - 예를 들어, GET 요청을 보내고 싶다면 perform(MockMvcRequestBuilders.get("/endpoint"))와 같이 요청을 설정합니다. - 파라미터 설정은 param("paramName", "paramValue")와 같이 파라미터를 설정할 수 있습니다.
3. TestDispatcher Servlet → Controller - 요청을 실행하고 응답을 받습니다. andExpect 메서드를 사용하여 응답의 상태코드, 헤더, 본문 등을 검증할 수 있습니다.
4. MockMvc → TestCase - 필요한 검증을 추가합니다. - 예를 들어, 응답 본문의 내용을 검증하고 싶다면 andExpect(content(). string("expectedValue"))와 같이 검증을 추가합니다.
- 스프링 프레임워크에서 제공하는 테스트용 라이브러리입니다. 이 라이브러리를 사용하면 Spring MVC 컨트롤러의 단위 테스트를 쉽게 작성할 수 있습니다. - MockMvc를 사용하면 HTTP 요청을 작성하고 컨트롤러의 응답을 검증할 수 있습니다. 이를 통해 통합 테스트를 실행하지 않고도 컨트롤러의 동작을 확인할 수 있습니다.
- SampleTest 클래스는 테스트를 수행하는 메서드인 sampleTest()를 가지고 있습니다. - 이 메서드에서는 /api/sample 엔드포인트에 대한 GET 요청을 수행하고, 응답의 상태 코드가 200 (OK)이며, JSON 응답의 message 필드가 "Hello, World!"인지를 검증합니다.
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
public class SampleTest {
private MockMvc mockMvc;
// ...
public void sampleTest() throws Exception {
ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders.get("/api/sample"));
resultActions
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("Hello, World!"));
// Add more assertions or actions as needed
}
}
3. MvcResult
💡 MvcResult
- MockMvc에서 수행된 MVC 요청의 결과에 대한 상세한 정보를 제공합니다. 이 클래스는 응답 상태, 헤더, 내용 등과 같은 정보를 추출하기 위한 다양한 메서드를 포함하고 있습니다.
- 주로 엔드포인트에 따른 요청에 따른 응답 값을 반환하고 있는지를 테스트합니다. 그렇기에 특정 API 엔드포인트에 요청 값을 담아서 보내면 응답값으로 전달이 되는지에 대해 확인합니다.
package com.adjh.multiflexapi.controller;
import com.adjh.multiflexapi.common.codes.SuccessCode;
import com.adjh.multiflexapi.common.response.ApiResponse;
import com.adjh.multiflexapi.model.CodeDto;
import com.adjh.multiflexapi.service.CodeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 코드 정보를 관리하는 Controller
*/
@Slf4j
@RestController
@RequestMapping(value = "/api/v1/code")
@Tag(name = "Code", description = "코드 API")
public class CodeController {
private final CodeService codeService;
public CodeController(CodeService codeService) {
this.codeService = codeService;
}
/**
* [API] 코드 리스트 출력
*
* @param codeDto codeDto
* @return ApiResponseWrapper<ApiResponse> : 응답 결과 및 응답 코드 반환
*/
@PostMapping("/codes")
@Operation(summary = "코드 조회", description = "코드 조회")
public ResponseEntity<ApiResponse<Object>> selectCodeList(@RequestBody @Validated CodeDto codeDto) {
log.debug("코드의 모든 리스트를 조회합니다.");
ApiResponse<Object> ar = ApiResponse.builder()
.result(codeDto)
.resultCode(SuccessCode.SELECT.getStatus())
.resultMsg(SuccessCode.SELECT.getMessage())
.build();
return new ResponseEntity<>(ar, HttpStatus.OK);
}
/**
* [API] 코드 단건 조회 : 코드 키 값을 기반으로 조회합니다.
*
* @param cd String
* @return ApiResponseWrapper<ApiResponse> : 응답 결과 및 응답 코드 반환
*/
@GetMapping("/code")
@Operation(summary = "코드 값 별 코드 조회", description = "코드 조회")
public ResponseEntity<ApiResponse<Object>> selectCodeByCd(@RequestParam String cd) {
CodeDto codeItem = codeService.selectCodeByCd(cd);
ApiResponse<Object> ar = ApiResponse.builder()
.result(codeItem)
.resultCode(SuccessCode.SELECT.getStatus())
.resultMsg(SuccessCode.SELECT.getMessage())
.build();
return new ResponseEntity<>(ar, HttpStatus.OK);
}
}
💡 위에 Restful api를 기반으로 CodeControllerTest.java 파일을 자동생성하여 아래와 같이 구성하였습니다.
- 자동생성 방법은 위에 환경 설정 부분을 참고하시면 됩니다.
2.1. Controller : 조회 테스트(요청 파라미터 전달)
💡 Controller : 조회 테스트
- 조회는 selectCodeList() 객체를 받아서 전체를 조회하는 메서드와 selectCodeByCd() String 문자열 키를 받아서 조회하는 메서드에 대해 테스트를 진행합니다.
💡 사용예시
- "/api/v1/code/code" 엔드포인트에 대한 GET 요청을 수행하고, "java"라는 코드 파라미터를 전달하여 테스트하고 있습니다. - 테스트 결과로는 MockMvc의 andExpect 메서드를 사용하여 상태코드가 200인지 확인하고, andDo 메서드를 사용하여 응답 내용을 출력하고 있습니다.
💡 [참고] @Mock CodeService codeService;를 선언하고 사용하지 않는데 아래와 같은 오류가 발생하는 이유는?
- codeService가 null인 상태에서 CodeController의 selectCodeByCd 메서드를 호출할 때 NullPointerException이 발생하기 때문입니다. - codeService는 CodeController의 의존성으로 주입되어야 하지만, codeService가 없는 상태로 테스트를 진행하면 codeService가 null로 초기화되어 있어서 해당 메서드를 호출할 수 없기 때문에 오류가 발생합니다.
2.2. Contoller : 조회 테스트(객체 전달)
💡 사용예시
- "/api/v1/code/codes" 엔드포인트에 대한 POST 요청을 수행하고, 객체를 전달하여 테스트하고 있습니다. - 테스트 결과로는 MockMvc의 andExpect 메서드를 사용하여 상태코드가 200인지 확인하고, andDo 메서드를 사용하여 응답 내용을 출력하고 있습니다.
- 해당 테스트 이전 구조에서는 다르게 Web Server를 직접적으로 사용하지 않지만 ‘Spring Context’까지 사용하여 테스트를 진행하는 방법입니다. - 해당 방식은 @WebMVCTest 어노테이션을 통해 MockMvc 인스턴스가 자동으로 구성되고 컨텍스트에서 사용이 가능해집니다.
- 클라이언트의 요청을 받아들이고, 처리한 결과를 클라이언트에게 전달하는 역할을 수행하는 소프트웨어입니다. - 웹 서버는 HTTP 프로토콜을 사용하여 클라이언트와 통신하며, 주로 웹 애플리케이션을 호스팅 하고 실행하는 역할을 합니다.
💡 스프링 컨텍스트(Spring Context)
- 스프링 프레임워크에서 제공하는 기능 중 하나로, 애플리케이션의 구성 요소들을 관리하고 제어하는 역할을 합니다. - 스프링 컨텍스트는 애플리케이션의 객체들을 생성하고 관리하며, 의존성 주입(Dependency Injection)을 통해 객체 간의 관계를 설정합니다.
💡 해당 테스트 구조를 본다면 웹 서버와 스프링 컨텍스트의 독립적인 관계로 이해하면 되나?
- 각각의 기능으로 봤을 때 스프링 컨텍스트는 웹 애플리케이션의 객체 관리와 제어에 사용되는 독립적인 컨테이너이며, 웹 서버는 클라이언트의 요청을 받아들이고 처리한 결과를 전달하는 역할을 수행합니다. - 따라서, 스프링 컨텍스트와 웹 서버는 서로 다른 역할을 가지고 있으며, 독립적으로 실행될 수 있습니다.
2. Controller 테스트 구조 및 사용 예시
💡 사용 예시 - 해당 테스트에서는 @WebMvcTest(CodeController.class)를 통해서 슬라이스 테스트를 진행하였습니다. - 이를 통해 Spring Context를 실행하여 테스트를 하며 이전에 standalone 방식을 사용하지 않고 수행하였습니다.
💡 아래와 같이 컨텍스트가 수행되었지만 ‘401 Unauthorized’ 에러가 발생한 것을 확인할 수 있습니다.
- 해당 문제가 발생한 이유는 해당 코드에서 @WebMvcTest(CodeController.class) 어노테이션이 사용되어 특정 컨트롤러만을 대상으로 하는 슬라이스 테스트를 수행하고 있기 때문입니다. - 이러한 슬라이스 테스트는 보안 및 인증과 관련된 기능을 포함하지 않습니다. 따라서, 인증되지 않은 요청이 발생하게 되면 401 Unauthorized 오류가 반환됩니다.
8) MockMvc 활용 예제 -3 : 웹 서버를 사용하여 테스트
1. 테스트 활용 구조
💡 테스트 활용 구조
- 해당 구조에서는 이전 구조와 다르게 Spring Context까지 사용했던 부분에서 이어나가 웹 서버까지 사용하여 테스트를 진행하는 방법입니다. - @SpringBootTest 어노테이션을 통해 실제 HTTP 서버로 테스트를 진행하는 방법입니다.