From eabd8a2964e84eba7bff9d6a50269d15a1f87d1f Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 25 Jan 2017 17:04:25 -0500 Subject: [PATCH] Support Servlet Part in Spring MVC Test Issue: SPR-14253 --- ...ockMultipartHttpServletRequestBuilder.java | 37 ++++++++++++- .../request/MockMvcRequestBuilders.java | 26 ++++++++- ...sts.java => MultipartControllerTests.java} | 55 +++++++++++++++---- src/asciidoc/testing.adoc | 2 +- 4 files changed, 104 insertions(+), 16 deletions(-) rename spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/{FileUploadControllerTests.java => MultipartControllerTests.java} (64%) diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMultipartHttpServletRequestBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMultipartHttpServletRequestBuilder.java index 33fb59f9a46..e026e0180e8 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMultipartHttpServletRequestBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMultipartHttpServletRequestBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,14 +18,20 @@ package org.springframework.test.web.servlet.request; import java.net.URI; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import javax.servlet.ServletContext; +import javax.servlet.http.Part; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartHttpServletRequest; +import org.springframework.util.Assert; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest; /** * Default builder for {@link MockMultipartHttpServletRequest}. @@ -38,6 +44,8 @@ public class MockMultipartHttpServletRequestBuilder extends MockHttpServletReque private final List files = new ArrayList<>(); + private final MultiValueMap parts = new LinkedMultiValueMap<>(); + /** * Package-private constructor. Use static factory methods in @@ -87,6 +95,19 @@ public class MockMultipartHttpServletRequestBuilder extends MockHttpServletReque return this; } + /** + * Add {@link Part} components to the request. + * @param parts one or more parts to add + * @since 5.0 + */ + public MockMultipartHttpServletRequestBuilder part(Part... parts) { + Assert.notEmpty(parts, "'parts' must not be empty"); + for (Part part : parts) { + this.parts.add(part.getName(), part); + } + return this; + } + @Override public Object merge(Object parent) { if (parent == null) { @@ -97,7 +118,10 @@ public class MockMultipartHttpServletRequestBuilder extends MockHttpServletReque if (parent instanceof MockMultipartHttpServletRequestBuilder) { MockMultipartHttpServletRequestBuilder parentBuilder = (MockMultipartHttpServletRequestBuilder) parent; this.files.addAll(parentBuilder.files); + parentBuilder.parts.keySet().stream().forEach(name -> + this.parts.putIfAbsent(name, parentBuilder.parts.get(name))); } + } else { throw new IllegalArgumentException("Cannot merge with [" + parent.getClass().getName() + "]"); @@ -112,10 +136,17 @@ public class MockMultipartHttpServletRequestBuilder extends MockHttpServletReque */ @Override protected final MockHttpServletRequest createServletRequest(ServletContext servletContext) { + MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest(servletContext); - for (MockMultipartFile file : this.files) { - request.addFile(file); + this.files.stream().forEach(request::addFile); + this.parts.values().stream().flatMap(Collection::stream).forEach(request::addPart); + + if (!this.parts.isEmpty()) { + new StandardMultipartHttpServletRequest(request) + .getMultiFileMap().values().stream().flatMap(Collection::stream) + .forEach(request::addFile); } + return request; } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java index 17322025a53..7f7f0534faf 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -208,7 +208,28 @@ public abstract class MockMvcRequestBuilders { * Create a {@link MockMultipartHttpServletRequestBuilder} for a multipart request. * @param urlTemplate a URL template; the resulting URL will be encoded * @param uriVars zero or more URI variables + * @since 5.0 */ + public static MockMultipartHttpServletRequestBuilder multipart(String urlTemplate, Object... uriVars) { + return new MockMultipartHttpServletRequestBuilder(urlTemplate, uriVars); + } + + /** + * Create a {@link MockMultipartHttpServletRequestBuilder} for a multipart request. + * @param uri the URL + * @since 5.0 + */ + public static MockMultipartHttpServletRequestBuilder multipart(URI uri) { + return new MockMultipartHttpServletRequestBuilder(uri); + } + + /** + * Create a {@link MockMultipartHttpServletRequestBuilder} for a multipart request. + * @param urlTemplate a URL template; the resulting URL will be encoded + * @param uriVars zero or more URI variables + * @deprecated in favor of {@link #multipart(String, Object...)} + */ + @Deprecated public static MockMultipartHttpServletRequestBuilder fileUpload(String urlTemplate, Object... uriVars) { return new MockMultipartHttpServletRequestBuilder(urlTemplate, uriVars); } @@ -217,11 +238,14 @@ public abstract class MockMvcRequestBuilders { * Create a {@link MockMultipartHttpServletRequestBuilder} for a multipart request. * @param uri the URL * @since 4.0.3 + * @deprecated in favor of {@link #multipart(URI)} */ + @Deprecated public static MockMultipartHttpServletRequestBuilder fileUpload(URI uri) { return new MockMultipartHttpServletRequestBuilder(uri); } + /** * Create a {@link RequestBuilder} for an async dispatch from the * {@link MvcResult} of the request that started async processing. diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/FileUploadControllerTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/MultipartControllerTests.java similarity index 64% rename from spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/FileUploadControllerTests.java rename to spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/MultipartControllerTests.java index 9b22dc942f0..b24325bd314 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/FileUploadControllerTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/MultipartControllerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.test.web.servlet.samples.standalone; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Collections; @@ -26,10 +27,13 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.Part; import org.junit.Test; +import org.springframework.http.MediaType; import org.springframework.mock.web.MockMultipartFile; +import org.springframework.mock.web.MockPart; import org.springframework.stereotype.Controller; import org.springframework.test.web.servlet.MockMvc; import org.springframework.ui.Model; @@ -40,14 +44,15 @@ import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.multipart.MultipartFile; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup; /** * @author Rossen Stoyanchev */ -public class FileUploadControllerTests { +public class MultipartControllerTests { @Test public void multipartRequest() throws Exception { @@ -57,8 +62,25 @@ public class FileUploadControllerTests { byte[] json = "{\"name\":\"yeeeah\"}".getBytes(StandardCharsets.UTF_8); MockMultipartFile jsonPart = new MockMultipartFile("json", "json", "application/json", json); - MockMvc mockMvc = standaloneSetup(new MultipartController()).build(); - mockMvc.perform(fileUpload("/test").file(filePart).file(jsonPart)) + standaloneSetup(new MultipartController()).build() + .perform(multipart("/test-multipartfile").file(filePart).file(jsonPart)) + .andExpect(status().isFound()) + .andExpect(model().attribute("fileContent", fileContent)) + .andExpect(model().attribute("jsonContent", Collections.singletonMap("name", "yeeeah"))); + } + + @Test + public void multipartRequestWithServletParts() throws Exception { + byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8); + MockPart filePart = new MockPart("file", "orig", new ByteArrayInputStream(fileContent)); + + byte[] json = "{\"name\":\"yeeeah\"}".getBytes(StandardCharsets.UTF_8); + MockPart jsonPart = new MockPart("json", "json", new ByteArrayInputStream(json)); + jsonPart.getHeaders().setContentType(MediaType.APPLICATION_JSON); + + standaloneSetup(new MultipartController()).build() + .perform(multipart("/test-multipartfile").part(filePart).part(jsonPart)) + .andExpect(status().isFound()) .andExpect(model().attribute("fileContent", fileContent)) .andExpect(model().attribute("jsonContent", Collections.singletonMap("name", "yeeeah"))); } @@ -72,15 +94,16 @@ public class FileUploadControllerTests { MockMvc mockMvc = standaloneSetup(new MultipartController()).addFilter(filter).build(); Map jsonMap = Collections.singletonMap("name", "yeeeah"); - mockMvc.perform(fileUpload("/testJson").file(jsonPart)).andExpect(model().attribute("json", jsonMap)); + mockMvc.perform(multipart("/test-json").file(jsonPart)).andExpect(model().attribute("json", jsonMap)); } @Controller + @SuppressWarnings("unused") private static class MultipartController { - @RequestMapping(value = "/test", method = RequestMethod.POST) - public String processMultipart(@RequestParam MultipartFile file, + @RequestMapping(value = "/test-multipartfile", method = RequestMethod.POST) + public String processMultipartFile(@RequestParam MultipartFile file, @RequestPart Map json, Model model) throws IOException { model.addAttribute("jsonContent", json); @@ -89,7 +112,17 @@ public class FileUploadControllerTests { return "redirect:/index"; } - @RequestMapping(value = "/testJson", method = RequestMethod.POST) + @RequestMapping(value = "/test-part", method = RequestMethod.POST) + public String processPart(@RequestParam Part part, + @RequestPart Map json, Model model) throws IOException { + + model.addAttribute("jsonContent", json); + model.addAttribute("fileContent", part.getInputStream()); + + return "redirect:/index"; + } + + @RequestMapping(value = "/test-json", method = RequestMethod.POST) public String processMultipart(@RequestPart Map json, Model model) { model.addAttribute("json", json); return "redirect:/index"; diff --git a/src/asciidoc/testing.adoc b/src/asciidoc/testing.adoc index ee444cd3596..17447a73508 100644 --- a/src/asciidoc/testing.adoc +++ b/src/asciidoc/testing.adoc @@ -3986,7 +3986,7 @@ request but rather you have to set it up: [source,java,indent=0] [subs="verbatim,quotes"] ---- - mockMvc.perform(fileUpload("/doc").file("a1", "ABC".getBytes("UTF-8"))); + mockMvc.perform(multipart("/doc").file("a1", "ABC".getBytes("UTF-8"))); ---- You can specify query parameters in URI template style: