반응형
해당 글에서는 MockMvc에 대해 이해하고 활용하는 방법에 대해 확인해 봅니다.
💡 [참고] 이전에 작성한 Test 관련 글들을 읽으시면 도움이 됩니다.
분류 | 링크 |
JUnit 5 이론 및 구성 요소 | https://adjh54.tistory.com/341 |
JUnit 5 환경구성 및 활용예제 | https://adjh54.tistory.com/342 |
JUnit 5 + Mockito 이론 및 활용예제 | https://adjh54.tistory.com/346 |
JUnit 5 + MockMvc 이론 및 활용예제 | https://adjh54.tistory.com/347 |
Assertions API Document | https://adjh54.tistory.com/348 |
개발방법론 TDD, BDD | https://adjh54.tistory.com/305 |
JUnit 5 + Mockito + MockMVC 사용예제 Github | https://github.com/adjh54ir/blog-codes/tree/main/spring-boot-test |
1) MockMvc
💡 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"))와 같이 검증을 추가합니다.
2) MockMvc, ResultActions, MvcResult 주요 메서드
1. 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"인지 확인합니다. |
💡 MockMvc 메서드를 이용한 사용예시
@ExtendWith(MockitoExtension.class) // Junit5 - Mockito 연동
class CodeControllerTest {
private MockMvc mockMvc; // HTTP 호출을 위한 MockMVC 사용
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(codeController).build();
}
@Test
public void testExample() throws Exception {
mockMvc.perform(get("/example")) // "/example"로 GET 요청 수행
.andExpect(status().isOk()) // 상태 코드 200인 성공적인 응답을 기대합니다
.andExpect(content().string("expected")) // 응답 내용이 "expected"와 같을 것으로 기대합니다
.andExpect(jsonPath("$.property").value("expected")) // JSON 속성 "property"의 값이 "expected"와 같을 것으로 기대합니다
.andExpect(view().name("expectedView")) // 뷰 이름이 "expectedView"와 같을 것으로 기대합니다
.andExpect(model().attribute("attributeName", "expectedValue")) // 모델 속성 "attributeName"의 값이 "expectedValue"와 같을 것으로 기대합니다
.andExpect(redirectedUrl("expectedUrl")); // "expectedUrl"로의 리다이렉트를 기대합니다
}
}
2. ResultActions
💡 ResultActions
- MockMvc를 사용하여 실행한 ‘HTTP 요청에 대한 결과’를 나타냅니다. 이를 통해 컨트롤러의 응답을 검증하고 원하는 동작을 수행할 수 있습니다.
메서드 | 설명 |
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() | 기본 핸들러로의 이전 전달을 뒤집습니다. |
💡 ResultActions 메서드 사용 예시
- 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 요청의 결과에 대한 상세한 정보를 제공합니다. 이 클래스는 응답 상태, 헤더, 내용 등과 같은 정보를 추출하기 위한 다양한 메서드를 포함하고 있습니다.
메서드 | 설명 |
getModelAndView() | 응답에 대한 모델과 뷰를 담고 있는 ModelAndView 객체를 반환합니다. |
getRequest() | 응답과 관련된 HttpServletRequest 객체를 반환합니다. |
getResponse() | 응답과 관련된 HttpServletResponse 객체를 반환합니다. |
getResponseHeaders() | 응답 헤더를 맵 형태로 반환합니다. |
getResponseStatus() | 응답 상태 코드를 반환합니다. |
getModel() | 응답으로부터 모델 객체를 반환합니다. |
getModelAndViewName() | ModelAndView 객체로부터 뷰의 이름을 반환합니다. |
💡 MvcResult 메서드 사용 예시
mvcResult.getModelAndView().getModel(); // ModelAndView 객체에서 모델을 가져옵니다.
mvcResult.getModelAndView().getModelAndViewName(); // ModelAndView 객체의 이름을 가져옵니다.
mvcResult.getRequest(); // 요청 객체를 가져옵니다.
mvcResult.getResponse(); // 응답 객체를 가져옵니다.
mvcResult.getResponseHeaders(); // 응답 헤더를 가져옵니다.
mvcResult.getResponseStatus(); // 응답 상태를 가져옵니다.
3) MockMvc에서 사용되는 어노테이션
💡 MockMvc에서 사용되는 어노테이션
- 해당 어노테이션은 JUnit5 어노테이션과 Mockito 어노테이션을 포함하였으며 테스트를 진행할 때 주로 사용되는 어노테이션 위주로 구성하였습니다.
어노테이션 | 설명 |
@ExtendWith(MockitoExtension.class) | Mockito를 사용하여 모킹하기 위해 테스트 클래스에 적용됩니다. |
@WebMvcTest | 웹 MVC 테스트를 위해 스프링 컨텍스트를 구성합니다. |
@AutoConfigureJsonTesters | JSON 테스트를 위해 JsonTester의 자동 구성을 활성화합니다. |
@AutoConfigureMockMvc | MockMvc를 자동으로 구성하는 데 사용됩니다. |
@Mock | 모킹 대상 객체를 생성하여 주입합니다. |
@MockBean | Spring 컨텍스트에서 Mock 객체를 생성하여 주입합니다. |
@SpringBootTest | 스프링 부트 애플리케이션의 통합 테스트를 위해 스프링 컨텍스트를 구성합니다. |
@BeforEach | 각각의 테스트 메서드가 실행되기 전에 실행되는 메서드를 지정합니다. |
@Test | 테스트 메서드를 지정합니다. |
4) 초기환경 구성
1. 의존성 주입
💡 의존성 주입
- 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
}
2. Controller 테스트를 위한 파일 생성: Go to… - Test 선택
3. 테스트 생성
4. 테스트 생성 파일 확인
5) MockMvc 단계 별 구성 과정 : Controller 테스트
1. Mock 초기화
💡 Mock 초기화
- MockMvc에서도 역시 Mock 객체를 초기화하는 데 사용됩니다. 이를 수행하면 테스트에서 Mock 객체를 사용할 수 있고 테스트를 실행할 때 예상된 동작을 가진 Mock 객체를 사용할 수 있습니다.
어노테이션 | 사용 버전 | 설명 |
@ExtendWith(MockitoExtension.class) | JUnit 5 | - Mockito를 사용하여 테스트 클래스를 초기화하는 데 사용됩니다. - MockitoExtension은 JUnit 5의 확장 기능으로, Mockito의 기능을 테스트 클래스에 적용합니다. |
💡 Mock는 Mockito와 함께 사용되므로 초기화를 수행합니다.
@ExtendWith(MockitoExtension.class)
class CodeControllerTest {
}
2. Mock Annotation & MockMvc 구성
💡Mock Annotation & MockMvc 구성
- MockMvc는 Controller를 테스트하는 데 사용되기에 Controller에 @InjectMocks으로 객체를 생성하고 인스턴스를 주입합니다.
1. @InjectMocks Controller
- @InjectMocks으로 객체를 생성하고 인스턴스를 주입합니다.
2. private MockMvc mockMvc
- MockMvc를 선언합니다.
3. @BeforeEach
- 선언한 MockMvc에 MockMvcBuilders.standaloneSetup(Class)를 통해 @Test가 실행되기 전에 독립적인 클래스(Controller)를 위해 선언하고 인스턴스를 생성하기 위해 구성합니다.
어노테이션 | 설명 |
@Mock | 모의 객체(Mock Object)를 생성하는데 사용됩니다. |
@InjectMocks | 모의 객체를 생성하고 인스턴스를 주입하는데 사용됩니다. |
@ExtendWith(MockitoExtension.class)
class CodeControllerTest {
@InjectMocks
private CodeController codeController;
private MockMvc mockMvc;
}
3. MockMvcBuilders.standaloneSetup(Class) 구성
💡 MockMvcBuilders.standaloneSetup(Class) 구성
- 선언한 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();
}
}
4. MockMvc 테스트 방식
테스트 방식 | 설명 |
Standalone 테스트 | 독립적으로 테스트를 수행한다는 것을 의미합니다. 다른 의존성 없이 테스트를 실행할 수 있으며, 외부 리소스나 서버에 대한 연결 없이 테스트를 수행할 수 있습니다. |
Spring Context를 수행하여 테스트 | Spring Context를 실행하여 테스트를 수행합니다. 이는 의존성 주입과 같은 Spring의 기능을 사용할 수 있게 해줍니다. |
Web Server를 수행하여 테스트 | Web Server를 실행하여 테스트를 수행합니다. 이는 실제 서버에 대한 테스트를 가능하게 해주며, 외부 리소스와의 상호작용을 테스트할 수 있습니다. |
6) MockMvc 활용 예제 -1: 웹 서버 없이 테스트
1. 테스트 활용 구조
💡 해당 테스트에서는 ‘웹 서버’와 Spring Context 수행 없이 컨트롤러 로직을 테스트하는 경우입니다.
2. Controller 테스트 구조 및 사용 예시
💡 Controller 테스트 구조
- 해당 테스트를 수행할 구조는 Controller에서 수행됩니다.
- 주로 엔드포인트에 따른 요청에 따른 응답 값을 반환하고 있는지를 테스트합니다. 그렇기에 특정 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 메서드를 사용하여 응답 내용을 출력하고 있습니다.
@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로 초기화되어 있어서 해당 메서드를 호출할 수 없기 때문에 오류가 발생합니다.
2.2. Contoller : 조회 테스트(객체 전달)
💡 사용예시
- "/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());
}
}
7) MockMvc 활용 예제 -2: Spring Context를 사용하여 테스트
1. 테스트 활용 구조
💡 테스트 활용 구조
- 해당 테스트 이전 구조에서는 다르게 Web Server를 직접적으로 사용하지 않지만 ‘Spring Context’까지 사용하여 테스트를 진행하는 방법입니다.
- 해당 방식은 @WebMVCTest 어노테이션을 통해 MockMvc 인스턴스가 자동으로 구성되고 컨텍스트에서 사용이 가능해집니다.
[ 더 알아보기 ]
💡 웹 서버(Web Server)
- 클라이언트의 요청을 받아들이고, 처리한 결과를 클라이언트에게 전달하는 역할을 수행하는 소프트웨어입니다.
- 웹 서버는 HTTP 프로토콜을 사용하여 클라이언트와 통신하며, 주로 웹 애플리케이션을 호스팅 하고 실행하는 역할을 합니다.
💡 스프링 컨텍스트(Spring Context)
- 스프링 프레임워크에서 제공하는 기능 중 하나로, 애플리케이션의 구성 요소들을 관리하고 제어하는 역할을 합니다.
- 스프링 컨텍스트는 애플리케이션의 객체들을 생성하고 관리하며, 의존성 주입(Dependency Injection)을 통해 객체 간의 관계를 설정합니다.
💡 해당 테스트 구조를 본다면 웹 서버와 스프링 컨텍스트의 독립적인 관계로 이해하면 되나?
- 각각의 기능으로 봤을 때 스프링 컨텍스트는 웹 애플리케이션의 객체 관리와 제어에 사용되는 독립적인 컨테이너이며, 웹 서버는 클라이언트의 요청을 받아들이고 처리한 결과를 전달하는 역할을 수행합니다.
- 따라서, 스프링 컨텍스트와 웹 서버는 서로 다른 역할을 가지고 있으며, 독립적으로 실행될 수 있습니다.
2. Controller 테스트 구조 및 사용 예시
💡 사용 예시
-
해당 테스트에서는 @WebMvcTest(CodeController.class)를 통해서 슬라이스 테스트를 진행하였습니다.
- 이를 통해 Spring Context를 실행하여 테스트를 하며 이전에 standalone 방식을 사용하지 않고 수행하였습니다.
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) 어노테이션이 사용되어 특정 컨트롤러만을 대상으로 하는 슬라이스 테스트를 수행하고 있기 때문입니다.
- 이러한 슬라이스 테스트는 보안 및 인증과 관련된 기능을 포함하지 않습니다. 따라서, 인증되지 않은 요청이 발생하게 되면 401 Unauthorized 오류가 반환됩니다.
8) MockMvc 활용 예제 -3 : 웹 서버를 사용하여 테스트
1. 테스트 활용 구조
💡 테스트 활용 구조
- 해당 구조에서는 이전 구조와 다르게 Spring Context까지 사용했던 부분에서 이어나가 웹 서버까지 사용하여 테스트를 진행하는 방법입니다.
- @SpringBootTest 어노테이션을 통해 실제 HTTP 서버로 테스트를 진행하는 방법입니다.
2. Controller 테스트 구조 및 사용 예시
💡 사용예시
- 해당 테스트에서는 @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 통신을 통해 호출이 되어 정상임을 테스트하였습니다.
💡 [참고] Spring Boot 환경에서 JUnit5를 이용하기 위한 관련 글들입니다.
분류 | URL |
JUnit 5 이론 및 구성 요소 | https://adjh54.tistory.com/341 |
JUnit 5 환경구성 및 활용예제 | https://adjh54.tistory.com/342 |
JUnit 5 + Mockito 이론 및 활용예제 | https://adjh54.tistory.com/346 |
JUnit 5 + MockMvc 이론 및 활용예제 | https://adjh54.tistory.com/347 |
Assertions API Document | https://adjh54.tistory.com/348 |
MockMvc, ResultActions, MvcResult API Document | https://adjh54.tistory.com/349 |
오늘도 감사합니다. 😀
반응형
'Java > Testing' 카테고리의 다른 글
[Java] Spring Boot JUnit5 이해하기 -3: @RepeatedTest, @ParameterizedTest를 이용한 반복 테스트 (0) | 2024.07.28 |
---|---|
[Java] Spring Boot Mockito 이해하기 : 테스트 흐름 및 사용예시 (0) | 2023.12.14 |
[Java] Spring Boot JUnit5 이해하기 -2 : 환경구성 및 활용 예제 (0) | 2023.12.08 |
[Java] Spring Boot JUnit5 이해하기 -1 : 이론 및 구조 (1) | 2023.12.07 |