If I want a post request definition like so:
@PostMapping(path = "/metadata/{id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
ResponseEntity<Void> test(HttpServletResponse response,//
@RequestPart(value = "file") MultipartFile file,
@RequestPart(value = "partName") Metadata partName,
@PathVariable(name = "id") String artifactId) {
return null;
}
(where Metadata is a simple Pojo) and then use the following test:
@Test
public void testMetadata() throws Exception {
mockMvc
.perform(multipart(HttpMethod.POST, "/metadata/123")
.file("file", "file content".getBytes())
.part(getJsonPart())
.contentType(MediaType.MULTIPART_FORM_DATA_VALUE))
.andExpect(status().isOk());
}
private @NotNull MockPart getJsonPart() throws JsonProcessingException {
byte[] partContent = new ObjectMapper().writeValueAsBytes(new Metadata("foo"));
MockPart part = new MockPart("partName", partContent);
part.getHeaders().setContentType(MediaType.APPLICATION_JSON);
return part;
}
It does not match - I get a 400 response instead of a 200. I think it's failing to deserialize the payload.
However, when I change my postmapping's part type from 'Metadata' to 'Object', it works (deserializing the part as a map).
Now I am probably being blind to something obvious, but am unable to resolve this on my own apparently. Any hints? Just to reiterate: what I am trying to get is a controller that has the proper deserialized 'Metadata' object.
Just for completeness, here are the 2 full files:
class Metadata {
private String value;
public Metadata(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
@RestController
public class TestController {
@PostMapping(path = "/metadata/{id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
ResponseEntity<Void> test(HttpServletResponse response,//
@RequestPart(value = "file") MultipartFile file,
@RequestPart(value = "partName") Metadata partName,
@PathVariable(name = "id") String artifactId) {
return null;
}
@PostMapping(path = "/object/{id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
ResponseEntity<Void> test(HttpServletResponse response, @RequestPart(value = "file") MultipartFile file,
@RequestPart(value = "partName") Object partName,
@PathVariable(name = "id") String artifactId) {
return null;
}
}
@WebMvcTest(TestController.class)
public class TestControllerTest {
@Autowired
public MockMvc mockMvc;
@Test
public void failingTest() throws Exception {
mockMvc
.perform(multipart(HttpMethod.POST, "/metadata/123")
.file("file", "file content".getBytes())
.part(getJsonPart())
.contentType(MediaType.MULTIPART_FORM_DATA_VALUE))
.andExpect(status().isOk());
}
@Test
public void passingTest() throws Exception {
mockMvc
.perform(multipart(HttpMethod.POST, "/object/123")
.file("file", "file content".getBytes())
.part(getJsonPart())
.contentType(MediaType.MULTIPART_FORM_DATA_VALUE))
.andExpect(status().isOk());
}
private @NotNull MockPart getJsonPart() throws JsonProcessingException {
byte[] partContent = new ObjectMapper().writeValueAsBytes(new Metadata("foo"));
MockPart part = new MockPart("partName", partContent);
part.getHeaders().setContentType(MediaType.APPLICATION_JSON);
return part;
}
}
Well, that was embarrasing...
The problem is the lack of a no-arg constructor, which causes deserialization to fail. But in this scenario, the error message of the deserializer gets lost and doesn't surface.
So, the solution is simply an empty constructor.