간만에 SpringBoot 기반의 Rest 애플리케이션을 테스트 하다 보니 한글 문제가 발생해서 대처 방안을 정리한다.
MockMVC 테스트 시 response content의 한글 깨짐 문제
MockMVC를 이용한 Rest 서비스의 단위 테스트
일반적으로 Service 등을 Mocking 하지 않고 그대로 사용하기 위해서는 아래와 같이 단위 테스트 할 수 있다.
@SpringBootTest
public class CatoryControllerTest {
@Autowired
CategoryController cController;
MockMvc mock;
@BeforeEach
public void setup() {
mock = MockMvcBuilders.standaloneSetup(cController).build();
}
@Test
public void findByNameTest() throws Exception {
mock.perform(get("/api/category").param("engName", "Backtracking"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.content[0].korName", equalTo("양방향 탐색")))
.andExpect(jsonPath("$.content.length()", is(7)));
}
}
갑자기 내용 전체를 출력해보고 싶었다.
단위테스트는 통과 했지만 불현듯 전체 반환 내용을 출력해보고 싶었다. 따라서 단위 테스트를 다음과 같이 변경해보있다.
@Test
public void findByNameTest() throws Exception {
MvcResult result =mock.perform(get("/api/category").param("engName", "search"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.content[0].korName", equalTo("양방향 탐색")))
.andExpect(jsonPath("$.content.length()", is(7)))
.andReturn();
String content = result.getResponse().getContentAsString();
System.out.println(content);
}
그리고 출력된 결과를 확인해봤더니 매우 신기한 결과가 나왔다.
{"content":[{"id":4451,"korName":"ìë°©í¥ íì","engName":"Bidirectional Search","pcats":null},
{"id":4308,"korName":"ì´ë¶ íì","engName":"Binary Search","pcats":null},
{"id":4309,"korName":"ëë¹ ì°ì íì","engName":"Breadth-first Search","pcats":null},
{"id":4313,"korName":"ê¹ì´ ì°ì íì","engName":"Depth-first Search","pcats":null},
{"id":4397,"korName":"ë³ë ¬ ì´ë¶ íì","engName":"Parallel Binary Search","pcats":null},
{"id":4366,"korName":"ë§¤ê° ë³ì íì","engName":"Parametric Search","pcats":null},
{"id":4383,"korName":"ì¼ë¶ íì","engName":"Ternary Search","pcats":null}]
,"pageable":{. . .}
}
위에서 보듯 content에 있는 korName이 깨져있는 것이다.
신기한 점은 jsonPath에서 비교했을 때는 어떻게 equalTo에서 true가 나왔을까이다. "양방향 탐색"과 "ìë°©í¥ íì"가 어떻게 같을까?
대책
정확한 문서는 찾지 못했지만 stackoverflow.com을 살펴보다보니 jsonPath는 언제나 utf-8로 변환해서 읽는다고 한다. 즉 아래처럼 사용한다는 이야기이다.
@Test
public void findByNameTest() throws Exception {
MvcResult result =mock.perform(get("/api/category").param("engName", "search"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.content[0].korName", equalTo("양방향 탐색")))
.andExpect(jsonPath("$.content.length()", is(7)))
.andReturn();
String content = result.getResponse().getContentAsString(Charset.forName("UTF-8"));
System.out.println(content);
}
UTF-8로 변환해버렸으니 당연히 결과는 제대로 출력된다.
{"content":[{"id":4451,"korName":"양방향 탐색","engName":"Bidirectional Search","pcats":null},
{"id":4308,"korName":"이분 탐색","engName":"Binary Search","pcats":null},
{"id":4309,"korName":"너비 우선 탐색","engName":"Breadth-first Search","pcats":null},
{"id":4313,"korName":"깊이 우선 탐색","engName":"Depth-first Search","pcats":null},
{"id":4397,"korName":"병렬 이분 탐색","engName":"Parallel Binary Search","pcats":null},
{"id":4366,"korName":"매개 변수 탐색","engName":"Parametric Search","pcats":null},
{"id":4383,"korName":"삼분 탐색","engName":"Ternary Search","pcats":null}],
"pageable":{...}
}
하지만 위의 코드는 응답을 가져온 후 다시 변환한 녀석이라 웬지 꺼림직하다.
보다 근본적인 방법은 MockMvc를 생성하면서 CharacterEncodingFilter를 삽입해주는 것이다.
@BeforeEach
public void setup() {
mock = MockMvcBuilders.standaloneSetup(cController)
.addFilter(new CharacterEncodingFilter("UTF-8", true))
.build();
}
@Test
public void findByNameTest() throws Exception {
MvcResult result =mock.perform(get("/api/category").param("engName", "search"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.content[0].korName", equalTo("양방향 탐색")))
.andExpect(jsonPath("$.content.length()", is(7)))
.andReturn();
// 결과를 별도로 encoding 처리하지 않는다.
String content = result.getResponse().getContentAsString();
System.out.println(content);
}
아마도
위와 같이 CharacterEncodingFilter를 적용해서 encoding 문제를 해결했지만 talend 같은 rest api tester 들을 이용해보면 별다른 조작 없이 한글 데이터가 잘 넘어온다.
이 문제를 곰곰히 생각해다 controller에서 response 객체를 확인해보니 일반적인 호출에서와 MockMvc를 통한 호출에서 HttpServletResponse 객체가 다르다는 것을 알게 되었다.
- 일반 호출: org.apache.catalina.connector.ResponseFacade
- MockMvc를 통한 호출: org.springframework.mock.web.MockHttpServletResponse
더이상 코드를 까보지는 않았지만 ResponseFacade는 utf-8로 처리하고 MockHttpServletResponse는 그렇지 않기 때문에 filter와 같은 보조 수단을 이용해서 encoding을 처리해줘야 하는것으로정리 하고자 한다. ㅜㅜ