Spring Boot MockMvc测试中俄语字符编码异常问题
MockMvc集成测试中俄语字符乱码问题
在使用MockMvc为Spring Boot应用编写集成测试时,遇到请求/响应体中的俄语字符乱码问题。Swagger或Postman测试应用时一切正常,但MockMvc测试中:
- GET请求返回的响应字符变为乱码(如
ТРМ-001变成ТРÐ-001) - POST请求的请求体俄语字符显示为
??????,导致后端校验失败返回400
已尝试在测试类属性、ObjectMapper、MockMvc中设置编码,修改迁移文件,均无效。
测试类配置
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = InfoApplicationConfig.class) @TestPropertySource(properties = {"spring.config.location=classpath:application-it.yml"}) @AutoConfigureMockMvc @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) public class TerminalControllerIT { @Autowired MockMvc mockMvc; @LocalServerPort private int port; private String baseUrl = "http://localhost"; private final String validTerminalId = "eee00000-0000-0000-0000-000000000000"; private final String invalidTerminalId = "abc00000-0000-0000-0000-000000123456"; private NewTerminalDto testSubject; @Autowired private static ObjectMapper objectMapper; @Autowired private TerminalRepository terminalRepository; @BeforeEach public void setUp() { testSubject = NewTerminalDto.builder().country("Россия").region("Москва").city("Москва") .street("Ленина").buildingNumber("12A") .roomNumber("101").terminalCoordinate("55.7558,37.6176") .postCode("123456").terminalNumber("123") .isClosed(false).cashDepositWithdrawal(false).moneyTransfer(true).payment(true).nfc(true) .banknotesPerPack(100) .biometrics(false).encashmentService(true).cashDeposit(true) .openingTime("08:00") .closingTime("18:00") .build(); baseUrl = baseUrl.concat(":" + port).concat("/api/v1/info-service/terminals"); } }
测试1:GET请求响应乱码
测试代码
@Test @DisplayName("Should return terminal by id") void getTerminalInfoByIdPositiveTest() throws Exception { MvcResult result = mockMvc.perform(get(baseUrl + "/" + validTerminalId) .characterEncoding("UTF-8") .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); String jsonResponse = result.getResponse().getContentAsString(); TerminalGetResponse actualResponse = objectMapper.readValue(jsonResponse, TerminalGetResponse.class); assertNotNull(actualResponse); assertNotNull(actualResponse.getTerminalNumber()); assertNotNull(actualResponse.getRegion()); assertEquals("ТРМ-001", actualResponse.getTerminalNumber()); assertEquals("Москва", actualResponse.getRegion()); }
测试结果
Expected :ТРМ-001 Actual :ТРÐ-001
测试2:POST请求体乱码
测试代码
@Test @DisplayName("Adding branch") void addBranch() throws Exception { BranchPayload body = BranchStubs.createBranchPayload(0); mockMvc.perform(post(baseUrl) .content(objectMapper.writeValueAsBytes(body)) .contentType(MediaType.APPLICATION_JSON_VALUE) .characterEncoding("UTF-8")) .andExpect(status().isOk()); BankBranchesModel bankBranchesModel = BankBranchesModel.builder().branchNumber(body.getBranchNumber()).build(); assertNotNull(bankBranchesRepository.findOne(Example.of(bankBranchesModel)).orElse(null)); }
测试结果
MockHttpServletRequest: HTTP Method = POST Request URI = /api/v1/info-service/branches Parameters = {} Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"907"] Body = {"branchNumber":"8500","country":"??????","region":"?????????? ???????","city":"????????","street":"???????????","buildingNumber":"5","postCode":"117997","branchCoordinate":"55.696225, 37.544539","ramp":true,"phoneNumber":"74955555550","isClosed":false,"openingTime":"00:00","closingTime":"23:55","dayOfWeek":["???????????","???????","?????","???????","???????"],"currencyExchange":true,"foreignCurrency":true,"moneyTransfer":true,"cashWithdrawal":true,"payment":true,"replenishCard":true,"replenishAccount":true,"hasDeposit":true,"hasCredit":true,"consultation":true,"insurance":true,"bik":"044525220","kpp":"773643002","inn":"7707083890","paymentAccount":"40702810562000000000","correspondentAccount":"30101810400000000225","bankNameFull":"?? «FinTech Bank»","okpo":"09610477","ogrn":"1027700057410","swift":"LIBBRUMM007"} Session Attrs = {} ... MockHttpServletResponse: Status = 400 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"bankNameFull":"must match \"^(?!\\s*$)[0-9A-Za-z?-??-? !\"#$%&'()*+,\\-./:;<=>?@\\[\\\\\\]^_{|}~]{1,30}$\""} Forwarded URL = null Redirected URL = null Cookies = []
解决方案
1. 全局配置MockMvc默认编码
通过自定义MockMvc Bean,强制设置响应编码为UTF-8:
@Bean public MockMvc mockMvc(WebApplicationContext webApplicationContext) { return MockMvcBuilders.webAppContextSetup(webApplicationContext) .defaultResponseCharacterEncoding(StandardCharsets.UTF_8) .build(); }
或者直接在@AutoConfigureMockMvc注解中指定字符集:
@AutoConfigureMockMvc(charset = "UTF-8")
2. 确保ObjectMapper使用UTF-8编码
配置ObjectMapper时明确指定UTF-8,避免序列化/反序列化时编码不一致:
@Bean public ObjectMapper objectMapper() { ObjectMapper objectMapper = new ObjectMapper(); // 其他配置... objectMapper.setCharset(StandardCharsets.UTF_8); objectMapper.getFactory().setCharset(StandardCharsets.UTF_8); return objectMapper; }
3. 优化GET请求的编码设置
在GET请求中,除了设置请求编码,还要指定接受的响应编码:
MvcResult result = mockMvc.perform(get(baseUrl + "/" + validTerminalId) .characterEncoding("UTF-8") .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON.withCharset(StandardCharsets.UTF_8))) .andExpect(status().isOk()) .andReturn();
4. 修改POST请求的内容传递方式
避免使用writeValueAsBytes,改用字符串形式传递请求体,并明确指定内容类型的编码:
mockMvc.perform(post(baseUrl) .content(objectMapper.writeValueAsString(body)) .contentType(MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8") .characterEncoding("UTF-8")) .andExpect(status().isOk());
5. 设置JVM启动参数
确保测试运行时JVM默认编码为UTF-8,添加启动参数:
-Dfile.encoding=UTF-8
内容的提问来源于stack exchange,提问作者D Bystrov
相关产品推荐
相关产品推荐

