MockMvc
는 스프링 프레임워크에서 제공하는 웹 애플리케이션 테스트용 라이브러리이다.MockMvc
의 핵심은 웹 애플리케이션의 다양한 컴포넌트를 테스트 수행이다.MockMvc
를 사용하면 HTTP 요청을 작성하고 컨트롤러의 응답을 검증할 수 있습니다. 이를 통해 통합 테스트를 실행하지 않고도 컨트롤러의 동작을 확인할 수 있습니다.
MockMvc
를 이용하여 컨트롤러의 동작을 테스트하는 데 사용된다. 좀 더 자세하게 설명하자면, 컨트롤러의 엔드포인트를 호출하여 HTTP 클라이언트의 요청을 모방하고 적절한 응답을 확인하기 위해 테스트를 수행하는 방식이다. 이러한 테스트 과정을 통해 애플리케이션의 서비스 로직이나 API 엔드포인트가 의도한 대로 동작하는지 확인하고, 버그를 발견하고 수정하는 데 큰 도움을 준다.
테스트 코드 → MockMvc
MockMvc
객체를 생성한다. MockMvc
객체는 테스트할 컨트롤러와 상호작용하게 된다.MockMvc
→ TestDispatcher Servlet
MockMvc
를 사용하여 원하는 엔드포인트에 요청을 보낸다. 또한 해당 요청에 필요한 파라미터, 헤더, 쿠키 등을 설정할 수 있다.perform(MockMvcRequestBuilders.get("/endpoint"))
와 같이 요청을 설정하면 된다.param("paramName", "paramValue")
와 같이 파라미터를 작성한다.TestDispatcher Servlet
→ Controller
andExpect
메서드를 사용하여 응답의 상태코드, 헤더, 본문 등을 검증을 수행한다.MockMvc
→ 테스트 코드
andExpect(content(). string("expectedValue"))
와 같이 검증을 추가한다.MockMvc
테스트 실행 시 요청부터 응답까지 의 처리 흐름은 다음 그림과 같다.
구분 | 설명 |
---|---|
(1) | 테스트 메서드는 Spring Test에서 제공하는org.springframework.test.web.servlet.TestDispatcherServlet 에서 요청할 데이터를 설정합니다. |
(2) | MockMvc 는 TestDispatcherServlet 에 의사 요청을 보냅니다. |
(3) | TestDispatcherServlet 에서 Controller 가 요청 세부 정보를 일치시키는 메서드를 호출합니다. |
(4) | 테스트 메서드는 MockMvc 에서 실행 결과를 받아 실행 결과의 유효성을 확인합니다. |
MockMvc
에 대해 정리하자면 다음과 같다.
MockMvc
는 스프링 프레임워크에서 제공하는 테스트용 라이브러리이다.MockMvc
라이브러리를 사용하면 Spring MVC 컨트롤러의 단위 테스트를 쉽게 작성할 수 있다.MockMvc
를 사용하면 HTTP 요청을 작성하고 컨트롤러의 응답을 검증할 수 있다.메서드 | 설명 |
---|---|
standaloneSetup() | 특정 컨트롤러를 MockMvc 에 설정하여테스트할 수 있는 환경을 구성 |
perform() | MockMvc 를 사용하여 HTTP 요청을 실행 |
andExpect() | 컨트롤러의 응답을 검증 |
andExpect(status().isOk()) | 응답 상태 코드가 200인지 확인 |
andExpect(content().string("expected")) | 응답 본문의 내용이 "expected" 인지 확인 |
andExpect(jsonPath("$.property").value("expected")) | JSON 응답에서 특정 속성의 값이 "expected" 인지 확인 |
andExpect(view().name("expectedView")) | 응답에 대한 뷰의 이름이 "expectedView" 인지 확인 |
andExpect(model().attribute("attributeName", "expectedValue")) | 모델 속성의 값이 "expectedValue" 인지 확인 |
andExpect(redirectedUrl("expectedUrl")) | 리다이렉트된 URL이 "expectedUrl" 인지 확인 |
@ExtendWith(MockitoExtension.class) // Junit5 - Mockito 연동
class MockMvcControllerTest {
private MockMvc mockMvc; // HTTP 호출을 위한 MockMVC 사용
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders
.standaloneSetup(codeController)
.build();
}
@Test
public void testExample() throws Exception {
mckMvc.perfrom(get("/example"))
.andExpect(status().isOk())
.andExpect(content().string("expected"))
.andExpect(jsonPath("$.property").value("expected"))
.andExpect(view().name("expectedView"))
.andExpect(model().attribute("attributeName", "expectedValue"))
.andExpect(redirectedUrl("expectedUrl"));
}
// 1. "/example"로 get 요청
mckMvc.perfrom(get("/example"))
// 2. 상태 코드 200 응답 확인
.andExpect(status().isOk())
// 3. 응답 내용이 "expected"인지 확인
.andExpect(content().string("expected"))
// 4. JSON의 "property" 속성값이 "expected"와 동일한지 확인
.andExpect(jsonPath("$.property").value("expected"))
// 5. 뷰 이름이 "expectedView"인지 확인
.andExpect(view().name("expectedView"))
// 6. 모델의 "attributeName" 속성값이 "expectedValue"인지 확인
.andExpect(model().attribute("attributeName", "expectedValue"))
// 7. "expectedUrl"으로 리다이렉트인지 확인
.andExpect(redirectedUrl("expectedUrl"));
@DisplayName("게시글 목록 조회하기")
@Test
public void Given_Nothing_When_RequestListOfArticle_Then_ReturnListOfArticle() throws Exception {
// When + Then
mvc.perform(get("/articles"))
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))
.andExpect(model().attributeExists("articles"));
}
게시글 목록 조회하는 테스트 코드에서
andExpect
메서드의 일부 코드가 변경되었다. 구체적으로 다음의 코드가 변경된다.andExpect(content().contentType(...)
에서andExpect(content().contentTypeCompatibleWith(...))
으로 변경해야 한다. 변경한 이유에 대해 알기 위해서 각 메서드 의미를 충분히 학습한다.
andExpect(content().contentType(...))
와 andExpect(content().contentTypeCompatibleWith(...))
는 두 가지 다른 메서드입니다. 이 두 메서드는 각각 다른 컨텐츠 타입 검증 기능을 제공합니다.
andExpect(content().contentType(...))
:
andExpect(content().contentType(MediaType.APPLICATION_JSON))
는 HTTP 응답의 컨텐츠 타입이 JSON인지를 확인합니다.mockMvc.perform(MockMvcRequestBuilders.get("/api/data"))
.andExpect(MockMvcResultMatchers.content()
.contentType(MediaType.APPLICATION_JSON));
andExpect(content().contentTypeCompatibleWith(...))
:
andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
는 HTTP 응답의 컨텐츠 타입이 JSON 또는 JSON과 호환되는지를 확인합니다.emockMvc.perform(MockMvcRequestBuilders.get("/api/data"))
.andExpect(MockMvcResultMatchers.content()
.contentTypeCompatibleWith(MediaType.APPLICATION_JSON));
이 두 메서드의 선택은 테스트 시나리오 및 검증의 목적에 따라 다릅니다. 대부분의 경우에는 contentTypeCompatibleWith
를 사용하여 더 유연한 검증을 수행할 수 있습니다. 특히, 컨텐츠 타입이 정확하게 일치하지 않더라도 호환되는 경우에 테스트를 통과하도록 할 때 유용합니다.
간단히 요약하자면, contentType()
는 엄격한 컨텐츠 타입을 검증합니다.
반면에 contentTypeCompatibleWith()
는 컨텐츠 타입이 호환되는 경우에도 검증에 성공합니다.
ResultActions
은 MockMvc
를 사용하여 실행한 HTTP 요청에 대한 결과를 나타낸다. ResultActions
을 통해 컨트롤러의 응답을 검증하고 원하는 동작을 수행할 수 있다.메서드 | 설명 |
---|---|
andReturn() | 해결된 MvcResult 객체를 반환 |
andReturn(MvcResult) | 반환할 MvcResult 를 설정 |
andDo(ResultHandler) | 결과에 대해 추가 작업을 수행 |
andDo(ResultMatcher) | 결과에 ResultMatcher 를 추가 |
andExpect(ResultMatcher) | 결과에 대한 기대치로 ResultMatcher 를 추가 |
andForward() | 요청을 다음 핸들러로 전달 |
andForward(String) | 요청을 지정된 URL로 전달 |
andExpect(MockMvcResultMatchers) | MockMvcResultMatchers 에서 ResultMatcher 를 추가 |
andExpect(MockMvcResultHandlers) | MockMvcResultHandlers 에서 ResultHandler 를 추가 |
andReverse() | 이전 전달을 뒤집음 |
andForwardDefault() | 요청을 기본 핸들러로 전달 |
andForwardDefault(String) | 요청을 지정된 URL로 기본 핸들러로 전달 |
andReturnDefault() | 기본 핸들러에 대한 해결된 MvcResult 객체를 반환 |
andReturnDefault(MvcResult) | 기본 핸들러에 반환할 MvcResult 를 설정 |
andDoDefault() | 기본 핸들러에 대해 추가 작업을 수행 |
andDoDefault(ResultHandler) | 지정된 핸들러를 사용하여 기본 핸들러에 대해 추가 작업을 수행 |
andDoDefault(ResultMatcher) | 기본 핸들러에 ResultMatcher 를 추가 |
andExpectDefault(ResultMatcher) | 기본 핸들러에 대한 결과 기대치로 ResultMatcher 를 추가 |
andForwardNamed(String) | 지정된 URL로 명명된 핸들러로 요청을 전달 |
andReturnNamed(String) | 명명된 핸들러에 대한 해결된 MvcResult 객체를 반환 |
andDoNamed(String) | 명명된 핸들러에서 추가 작업을 수행 |
andDoNamed(String, ResultHandler) | 지정된 핸들러를 사용하여 명명된 핸들러에서 추가 작업을 수행 |
andDoNamed(String, ResultMatcher) | 명명된 핸들러에 ResultMatcher 를 추가 |
andExpectNamed(String, ResultMatcher) | 명명된 핸들러에 대한 결과 기대치로 ResultMatcher 를 추가 |
andReverseNamed(String) | 이전 명명된 핸들러로의 전달을 뒤집음 |
andReverseDefault() | 기본 핸들러로의 이전 전달을 뒤집음 |
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
}
}
SampleTest
클래스는 테스트를 수행하는 메서드인 sampleTest()
를 가지고 있다./api/sample
엔드포인트에 대한 GET 요청을 수행하고, 응답의 상태 코드가 200 (OK)이며, JSON 응답의 message
필드가 "Hello, World!"
인지를 검증한다.MvcResult
는 MockMvc에서 수행된 MVC 요청의 결과에 대한 상세한 정보를 제공한다. MvcResult
클래스는 응답 상태, 헤더, 내용 등과 같은 정보를 추출하기 위한 다양한 메서드를 포함한다.메서드 | 설명 |
---|---|
getModelAndView() | 응답에 대한 모델과 뷰를 담고 있는 ModelAndView 객체를 반환 |
getRequest() | 응답과 관련된 HttpServletRequest 객체를 반환 |
getResponse() | 응답과 관련된 HttpServletResponse 객체를 반환 |
getResponseHeaders() | 응답 헤더를 맵 형태로 반환 |
getResponseStatus() | 응답 상태 코드를 반환 |
getModel() | 응답으로부터 모델 객체를 반환 |
getModelAndViewName() | ModelAndView 객체로부터 뷰의 이름을 반환 |
// ModelAndView 객체에서 모델을 가져온다.
mvcResult.getModelAndView().getModel();
// ModelAndView 객체의 이름을 가져온다.
mvcResult.getModelAndView().getModelAndViewName();
mvcResult.getRequest(); // 요청 객체를 가져온다.
mvcResult.getResponse(); // 응답 객체를 가져온다.
mvcResult.getResponseHeaders(); // 응답 헤더를 가져온다.
mvcResult.getResponseStatus(); // 응답 상태를 가져온다.
MockMvc
의 어노테이션은 JUnit5 어노테이션과 Mockito 어노테이션을 포함하였으며 테스트를 진행할 때 주로 사용되는 어노테이션 위주로 구성되었습니다.
어노테이션 | 설명 |
---|---|
@ExtendWith(MockitoExtension.class) | Mockito를 사용하여 모킹하기 위해 테스트 클래스에 적용 |
@WebMvcTest | 웹 MVC 테스트를 위해 스프링 컨텍스트를 구성 |
@AutoConfigureJsonTesters | JSON 테스트를 위해 JsonTester의 자동 구성을 활성화 |
@AutoConfigureMockMvc | MockMvc를 자동으로 구성하는 데 사용 |
@Mock | 모킹 대상 객체를 생성하여 주입 |
@MockBean | Spring 컨텍스트에서 Mock 객체를 생성하여 주입 |
@SpringBootTest | 스프링 부트 애플리케이션의 통합 테스트를 위해 스프링 컨텍스트를 구성 |
@BeforEach | 각각의 테스트 메서드가 실행되기 전에 실행되는 메서드를 지정 |
@Test | 테스트 메서드를 지정 |
MockMVC
를 사용하기 위해서는 ‘spring-boot-starter-test’
라이브러리를 추가해야 한다.Mockito
라이브러리 기능도 함께 사용하기에 의존성을 추가한다.dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test' // Spring Boot Starter Test
testImplementation 'org.mockito:mockito-core:5.8.0' // Mockito Core
}
MockMvc
에서도 역시 Mock
객체를 초기화하는 데 사용된다. 이를 수행하면 테스트에서 Mock
객체를 사용할 수 있고 테스트를 실행할 때 예상된 동작을 가진 Mock
객체를 사용할 수 있다.
어노테이션 | 사용 버전 | 설명 |
---|---|---|
@ExtendWith(MockitoExtension.class) | JUnit 5 | - Mockito 를 사용하여 테스트 클래스를 초기화하는 데 사용- MockitoExtension 은 JUnit 5의 확장 기능으로,Mockito 의 기능을 테스트 클래스에 적용 |
Mock
는 Mockito
와 함께 사용되므로 초기화를 수행한다.
@ExtendWith(MockitoExtension.class)
class CodeControllerTest {
}
MockMvc
는 Controller
를 테스트하는 데 사용되기에 Controller
에 @InjectMocks
으로 객체를 생성하고 인스턴스를 주입한다.@InjectMocks Controller
: @InjectMocks
으로 객체를 생성하고 인스턴스를 주입한다.private MockMvc mockMvc
: MockMvc를 선언한다.@BeforeEach
: 선언한 MockMvc
에 MockMvcBuilders.standaloneSetup(Class)
를 통해 @Test
가 실행되기 전에 독립적인 클래스(Controller)를 위해 선언하고 인스턴스를 생성하기 위해 구성한다.어노테이션 | 설명 |
---|---|
@Mock | 모의 객체(Mock Object)를 생성하는데 사용 |
@InjectMocks | 모의 객체를 생성하고 인스턴스를 주입하는데 사용 |
@ExtendWith(MockitoExtension.class)
class CodeControllerTest {
@InjectMocks
private CodeController codeController;
private MockMvc mockMvc;
}
선언된 MockMvc에 MockMvcBuilders.standaloneSetup(Class)에 의해 @Test가 실행되기 전, 독립적인 클래스(Controller)를 위해 선언하며 또한 인스턴스를 생성하기 위해 구성합니다.
@ExtendWith(MockitoExtension.class)
class CodeControllerTest {
@InjectMocks
private CodeController codeController;
private MockMvc mockMvc;
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders
.standaloneSetup(codeController)
.build();
}
}
테스트 방식 | 설명 |
---|---|
Standalone 테스트 | - 독립적으로 테스트를 수행한다는 것을 의미 - 다른 의존성 없이 테스트를 실행할 수 있음 - 외부 리소스나 서버에 대한 연결 없이 테스트를 수행이 가능 |
Spring Context를 수행하여 테스트 | - Spring Context를 실행하여 테스트를 수행 - 의존성 주입과 같은 Spring의 기능 사용이 가능 |
Web Server를 수행하여 테스트 | - Web Server를 실행하여 테스트를 수행 - 실제 서버에 대한 테스트를 가능 - 외부 리소스와의 상호작용을 테스트가 가능 |
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 파일을 자동생성하여 아래와 같이 구성하였습니다.
Controller : 조회 테스트는 selectCodeList()
객체를 받아서 전체를 조회하는 메서드와 selectCodeByCd() String
문자열 키를 받아서 조회하는 메서드에 대해 테스트를 진행합니다.
사용예시
"/api/v1/code/code"
엔드포인트에 대한 GET 요청을 수행하고, "java"라는 코드 파라미터를 전달하여 테스트하고 있습니다.MockMvc
의 andExpect
메서드를 사용하여 상태코드가 200인지 확인하고, andDo
메서드를 사용하여 응답 내용을 출력하고 있습니다.@Slf4j
@ExtendWith(MockitoExtension.class)
class CodeControllerTest {
@InjectMocks
private CodeController codeController;
@Mock
private CodeService codeService;
private MockMvc mockMvc;
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(codeController).build();
}
@Test
@DisplayName("코드 단건을 조회합니다.")
void selectCodeByCd() throws Exception {
// given
String paramCd = "java";
// when
ResultActions resultActions = mockMvc.perform(
MockMvcRequestBuilders.get("/api/v1/code/code")
.param("cd", paramCd)
.contentType(MediaType.APPLICATION_JSON));
// then
MvcResult mvcResult = resultActions
.andExpect(status().isOk())
.andDo(print())
.andReturn();
System.out.println("mvcResult :: " + mvcResult.getResponse().getContentAsString());
}
}
[참고] 필수 파라미터를 전달하지 않은 경우
[참고] @Mock CodeService codeService;
를 선언하고 사용하지 않는데 아래와 같은 오류가 발생하는 이유는?
codeService
가 null
인 상태에서 CodeController
의 selectCodeByCd
메서드를 호출할 때 NullPointerException
이 발생하기 때문입니다. codeService
는 CodeController
의 의존성으로 주입되어야 하지만, codeService
가 없는 상태로 테스트를 진행하면 codeService
가 null
로 초기화되어 있어서 해당 메서드를 호출할 수 없기 때문에 오류가 발생합니다.사용예시
"/api/v1/code/codes"
엔드포인트에 대한 POST 요청을 수행하고, 객체를 전달하여 테스트하고 있습니다.MockMvc
의 andExpect
메서드를 사용하여 상태코드가 200인지 확인하고, andDo
메서드를 사용하여 응답 내용을 출력하고 있습니다.@Slf4j
@ExtendWith(MockitoExtension.class)
class CodeControllerTest {
@InjectMocks
private CodeController codeController;
@Mock
private CodeService codeService;
private MockMvc mockMvc;
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(codeController).build();
}
@Test
@DisplayName("코드 전체 리스트를 조회합니다.")
void selectCodeListTest() throws Exception {
// given
CodeDto codeDto = CodeDto.builder().cd("java").build();
// when
ResultActions resultActions = mockMvc.perform(
MockMvcRequestBuilders.post("/api/v1/code/codes")
.contentType(MediaType.APPLICATION_JSON)
.content(new Gson().toJson(codeDto)));
// then
MvcResult mvcResult = resultActions
.andExpect(status().isOk())
.andDo(print())
.andReturn();
System.out.println("mvcResult :: " + mvcResult.getResponse().getContentAsString());
}
}
테스트 활용 구조
- 해당 테스트 이전 구조에서는 다르게 Web Server를 직접적으로 사용하지 않지만 ‘Spring Context’까지 사용하여 테스트를 진행하는 방법입니다.
- 해당 방식은 @WebMVCTest
어노테이션을 통해 MockMvc
인스턴스가 자동으로 구성되고 컨텍스트에서 사용이 가능해집니다.
[ 더 알아보기 ]
💡 웹 서버(Web Server)
💡 스프링 컨텍스트(Spring Context)
💡 해당 테스트 구조를 본다면 웹 서버와 스프링 컨텍스트의 독립적인 관계로 이해하면 되나?
사용 예시
@WebMvcTest(CodeController.class)
를 통해서 슬라이스 테스트를 진행하였습니다.package com.adjh.multiflexapi.controller;
import com.adjh.multiflexapi.model.CodeDto;
import com.adjh.multiflexapi.service.CodeService;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
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.MvcResult;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* MockMvc 활용하여 Spring Context를 실행시켜 테스트
*
* @author : lee
* @fileName : CodeControllerSpringContext
* @since : 12/14/23
*/
@Slf4j
@AutoConfigureJsonTesters
@WebMvcTest(CodeController.class)
class CodeControllerSpringContext {
@MockBean
private CodeController codeController;
@Mock
private CodeService codeService;
@Autowired
private MockMvc mockMvc;
@BeforeEach
void setUp() {
}
@Test
@DisplayName("코드 단건을 조회합니다.")
void selectCodeByCd() throws Exception {
// given
String paramCd = "java";
// when
ResultActions resultActions = mockMvc.perform(
MockMvcRequestBuilders.get("/api/v1/code/code")
.param("cd", paramCd)
.contentType(MediaType.APPLICATION_JSON));
// then
MvcResult mvcResult = resultActions
.andExpect(status().isOk())
.andDo(print())
.andReturn();
System.out.println("mvcResult :: " + mvcResult.getResponse().getContentAsString());
}
@Test
@DisplayName("코드 전체 리스트를 조회합니다.")
void selectCodeListTest() throws Exception {
// given
CodeDto codeDto = CodeDto.builder().cd("java").build();
// when
ResultActions resultActions = mockMvc.perform(
MockMvcRequestBuilders.post("/api/v1/code/codes")
.contentType(MediaType.APPLICATION_JSON)
.content(new Gson().toJson(codeDto)));
// then
MvcResult mvcResult = resultActions
.andExpect(status().isOk())
.andDo(print())
.andReturn();
System.out.println("mvcResult :: " + mvcResult.getResponse().getContentAsString());
}
}
💡 아래와 같이 컨텍스트가 수행되었지만 ‘401 Unauthorized’ 에러가 발생한 것을 확인할 수 있습니다.
@WebMvcTest(CodeController.class)
어노테이션이 사용되어 특정 컨트롤러만을 대상으로 하는 슬라이스 테스트를 수행하고 있기 때문입니다.테스트 활용 구조
@SpringBootTest
어노테이션을 통해 실제 HTTP 서버로 테스트를 진행하는 방법입니다.사용예시
@SpringBootTest
어노테이션을 통해서 Web Server를 수행하도록 테스트를 진행하였습니다.TestRestTemplate
을 통해서 HTTP 요청을 보내고 응답받는 형태로 체크를 하는 형태로 수행하였습니다.package com.adjh.multiflexapi.controller;
import com.adjh.multiflexapi.model.CodeDto;
import com.adjh.multiflexapi.service.CodeService;
import groovy.util.logging.Slf4j;
import org.junit.jupiter.api.Assertions;
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.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import org.springframework.test.web.servlet.MockMvc;
/**
* Web Server 수행시켜 테스트를 하는 경우
*
* @author : lee
* @fileName : CodeControllerWebServerTest
* @since : 12/14/23
*/
@Slf4j
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
class CodeControllerWebServerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private CodeService codeService;
@Autowired
private TestRestTemplate restTemplate;
@Test
@DisplayName("코드 단건을 조회합니다.")
void selectCodeByCd() throws Exception {
// given
String paramCd = "java";
// when
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer xxxxxxx");
// then
HttpEntity<String> entity = new HttpEntity<>(headers);
String url = String.format("/api/v1/code/code?cd=%s", paramCd);
ResponseEntity<CodeDto> codeDtoResult = restTemplate.exchange(url, HttpMethod.GET, entity, CodeDto.class);
Assertions.assertEquals(codeDtoResult.getStatusCode(), HttpStatus.OK);
System.out.println("codeDtoResult ::" + codeDtoResult);
}
@Test
@DisplayName("코드 전체 리스트를 조회합니다.")
void selectCodeListTest() throws Exception {
// given
CodeDto codeDto = CodeDto.builder().cd("java").build();
// when
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer xxxxxx");
// then
HttpEntity<CodeDto> entity = new HttpEntity<>(codeDto, headers);
ResponseEntity<CodeDto> codeDtoResult = restTemplate.exchange("/api/v1/code/codes", HttpMethod.POST, entity, CodeDto.class);
Assertions.assertEquals(codeDtoResult.getStatusCode(), HttpStatus.OK);
System.out.println("codeDtoResult ::" + codeDtoResult);
}
}
사용 결과
TestRestTemplate
을 통해서 HTTP 통신을 통해 호출이 되어 정상임을 테스트하였습니다.