MockRestServiceServer测试Multipart-formdata文件上传的问题排查
解决Multipart Form Data请求的单元测试问题
针对你遇到的两个核心问题,我来一步步帮你解决:
问题1:处理动态变化的Content-Type(含boundary和charset)
当发送multipart请求时,Spring的RestTemplate会自动生成唯一的boundary并添加到Content-Type头中,所以测试时没法用固定字符串做精确断言。我们可以用Hamcrest匹配器来检查Content-Type是否包含multipart/form-data,不用纠结动态生成的boundary部分。
修改测试类中的createServiceStub方法,把固定的Content-Type断言替换成匹配器:
import org.hamcrest.Matchers; import org.springframework.http.HttpHeaders; private ResponseActions createServiceStub(HttpMethod method, String url) { // 移除固定contenType参数 String encodedCredentials = Base64.getEncoder().encodeToString((username + ":" + password).getBytes()); ResponseActions stub = server.expect(ExpectedCount.once(), requestTo(url)); stub = stub.andExpect(method(method)); // 断言Content-Type包含multipart/form-data,可选验证charset stub = stub.andExpect(header(HttpHeaders.CONTENT_TYPE, Matchers.allOf( Matchers.containsString(MediaType.MULTIPART_FORM_DATA_VALUE), Matchers.containsString("charset=UTF-8") // 根据你的实际需求选择是否保留 ))); stub = stub.andExpect(header("Authorization", "Basic " + encodedCredentials)); return stub; }
另外建议调整Service类的createRequestHeaders方法:发送multipart请求时,不需要手动设置Content-Type,RestTemplate会自动处理(包括生成合法的boundary)。你可以改成:
// 修改后的createRequestHeaders private HttpHeaders createRequestHeaders(JiraClient jiraClient) { // 移除contentType参数 HttpHeaders headers = new HttpHeaders(); headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); headers.setBasicAuth(jiraClient.getUsername(), jiraClient.getPassword()); return headers; } // 在addAttachment中调用 HttpHeaders headers = createRequestHeaders(jiraClient); headers.set("X-Atlassian-Token", "no-check"); // 不再手动设置ContentType,交给RestTemplate自动处理
这样RestTemplate会自动生成正确的Content-Type头,测试时用上面的匹配器断言即可。
问题2:断言请求的Payload内容一致
要验证multipart请求中的文件内容和预期一致,我们需要解析请求的multipart内容,逐个检查每个part的文件名、内容等信息。可以通过自定义RequestMatcher实现:
在你的测试方法中添加payload断言逻辑:
import org.springframework.mock.web.MockMultipartHttpServletRequest; import org.springframework.mock.web.MockMultipartFile; import java.nio.file.Files; import java.nio.file.Paths; @Test public void addAttachment_WithValidData_ShouldAddAttachmentToJiraIssue() throws Exception { String url = host + "/rest/api/2/issue/" + issueKey + "/attachments"; ResponseActions stub = createServiceStub(HttpMethod.POST, url); stub = stub.andExpect(header("X-Atlassian-Token", "no-check")); // 添加Payload断言:验证文件part的内容和预期一致 stub = stub.andExpect(request -> { // 将请求转换为MockMultipartHttpServletRequest以便解析 MockMultipartHttpServletRequest multipartRequest = new MockMultipartHttpServletRequest(); multipartRequest.setContent(request.getBody()); multipartRequest.setContentType(request.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE)); // 获取名为"file"的part MockMultipartFile filePart = (MockMultipartFile) multipartRequest.getFile("file"); assertThat(filePart).isNotNull("请求中必须包含file部分"); // 验证文件名 assertThat(filePart.getOriginalFilename()) .isEqualTo(attachmentFileSystemResource.getFilename()); // 验证文件内容和预期的resource一致 byte[] expectedContent = Files.readAllBytes(Paths.get(validJiraAttachmentAddition.getURI())); assertThat(filePart.getBytes()).isEqualTo(expectedContent); }); stub.andRespond(withSuccess()); when(mockJiraAttachment.downloadFileFromWeb()).thenReturn(attachmentFileSystemResource); jiraService.addAttachment(mockJiraClient, mockJiraIssue, mockJiraAttachment); }
补充说明:
- 确保
attachmentFileSystemResource指向你的测试文件,validJiraAttachmentAddition是预期的文件内容资源。 - 如果需要验证多个part,在
requestlambda中继续获取其他part并添加断言即可。
完整修改后的测试类关键片段
@ActiveProfiles("test") @RestClientTest(JiraService.class) public class JiraServiceTest { @Value("classpath:jira/add_attachment/validJiraAttachmentAddition.json") private Resource validJiraAttachmentAddition; @Autowired private MockRestServiceServer server; @Autowired private JiraService jiraService; @Mock private JiraAttachment mockJiraAttachment; private FileSystemResource attachmentFileSystemResource; private String host = "https://your-jira-test-instance.com"; private String issueKey = "TEST-123"; private String username = "test-user"; private String password = "test-pass"; @BeforeEach public void setupTests() throws IOException { // 初始化测试文件资源和mock attachmentFileSystemResource = new FileSystemResource("src/test/resources/test-attachment.txt"); MockitoAnnotations.openMocks(this); } @Test public void addAttachment_WithValidData_ShouldAddAttachmentToJiraIssue() throws Exception { String url = host + "/rest/api/2/issue/" + issueKey + "/attachments"; ResponseActions stub = createServiceStub(HttpMethod.POST, url); stub = stub.andExpect(header("X-Atlassian-Token", "no-check")); // 断言请求文件内容 stub = stub.andExpect(request -> { MockMultipartHttpServletRequest multipartRequest = new MockMultipartHttpServletRequest(); multipartRequest.setContent(request.getBody()); multipartRequest.setContentType(request.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE)); MockMultipartFile filePart = (MockMultipartFile) multipartRequest.getFile("file"); assertThat(filePart).isNotNull(); assertThat(filePart.getOriginalFilename()).isEqualTo(attachmentFileSystemResource.getFilename()); byte[] expectedContent = Files.readAllBytes(Paths.get(validJiraAttachmentAddition.getURI())); assertThat(filePart.getBytes()).isEqualTo(expectedContent); }); stub.andRespond(withSuccess()); when(mockJiraAttachment.downloadFileFromWeb()).thenReturn(attachmentFileSystemResource); jiraService.addAttachment(mockJiraClient, mockJiraIssue, mockJiraAttachment); } private ResponseActions createServiceStub(HttpMethod method, String url) { String encodedCredentials = Base64.getEncoder().encodeToString((username + ":" + password).getBytes()); ResponseActions stub = server.expect(ExpectedCount.once(), requestTo(url)); stub = stub.andExpect(method(method)); stub = stub.andExpect(header(HttpHeaders.CONTENT_TYPE, Matchers.allOf( Matchers.containsString(MediaType.MULTIPART_FORM_DATA_VALUE), Matchers.containsString("charset=UTF-8") ))); stub = stub.andExpect(header("Authorization", "Basic " + encodedCredentials)); return stub; } }
这样就能解决你遇到的两个问题:既可以正确断言动态的Content-Type头,又能验证请求中的文件内容是否符合预期。
内容的提问来源于stack exchange,提问作者Pedro Pereira
相关产品推荐
相关产品推荐

