DEV Community

Shiveen Pandita
Shiveen Pandita

Posted on

Sending Multipart Form Data Using Spring WebTestClient

(x-posted from personal blog here)

Background

For the past year or so, I have been working extensively with spring, especially spring webflux; building scalable reactive micro services for our customers.

Coming from spring MVC, learning webflux and getting used to reactive programming in general has been a great and worthy learning experience and I highly suggest going through the references section if you haven’t heard of reactive programming and/or have been thinking about giving it a go and don’t know where to start. But essentially reactive programming involves a model of creating, requesting and manipulating data in a controllable (from a consumers perspective) and non-blocking manner.

WebTestClient is a reactive testing high level http client with fluent assertions, packaged in spring web flux. Recently, while integration testing an application that accepted data as multipart/form-data I had to figure out how to test the data effectively using the webtestclient and personally found the lack of comprehensive resources on the internet lacking, so I wrote this blogpost to share my own learnings.

Web Form Testing with Webflux

Let’s suppose that we’re trying to send the request to fill a form api that accepts a document (image, text, plain binary etc.) and some textual data.

To aid with our example, lets imagine the form is a profile setup for an document share service and takes the following input:

  • Profile Image (api label: profileImage)
  • Username (api label: username)
  • Email (api label: email)
  • PDF document to share (api label: userDocument)

For us to begin sending the data, we’ll use the spring library called MultipartBodyBuilder which provides a nice api for setting up the body for multipart requests.

To send the first part, the profile image we can set it up as:

val bodyBuilder = MultipartBodyBuilder()

bodyBuilder.part("profileImage", ClassPathResource("test-image.jpg").file.readBytes()).header("Content-Disposition", "form-data; name=profileImage; filename=profile-image.jpg")
Enter fullscreen mode Exit fullscreen mode

To explain a bit about what’s going on there, we’re simply telling the body builder to upload an image found in src/test/resources folder with the name test-image.jpg as the profile image part of this body. The real kicker here is setting up the Header part as that is what’s used by the webtestclient internals (specifically the Synchronoss-nio library which webflux uses internally) to determine the type of form data being sent and how to process it.

Also, note that the real file name that will get uploaded in the web server receiving the request is the profile-image.jpg filename that gets sent as part of the headers,.

Similar to the profile image, we can also send the document part of the whole request payload:

bodyBuilder.part("userDocument", ClassPathResource("user-document.pdf").file.readBytes()).header("Content-Disposition", "form-data; name=userDocument; filename=my-thesis.pdf")
Enter fullscreen mode Exit fullscreen mode

Similar to the previous payload we test the body builder 💪 to read a file in the test resources folder called user-document.pdf as bytes and send the document with the name my-thesis.pdf to the form web api.

As you can already see, compared to some other ways of doing it, such as in this excellent blog , using the MultipartBodyBuilder is rather conveneient.

Now for the last two remaining pieces of the form api, which are usually only plain text, we can set them up as:

bodyBuilder.part("username", "shiveenpandita", MediaType.TEXT_PLAIN).header("Content-Disposition", "form-data; name=username").header("Content-type", "text/plain")

bodyBuilder.part("email", "shiveenpandita@gmail.com", MediaType.TEXT_PLAIN).header("Content-Disposition", "form-data; name=email").header("Content-type", "text/plain")
Enter fullscreen mode Exit fullscreen mode

Woohoo! 🎉 We’ve got all our form fields wired now.

Now to see it all in action and bring it all together, we can simply setup a spring integration test and use our freshly setup body builder as:

@RunWith(SpringRunner::class.java)
@SpringBootTest
@AutoConfigureWebTestClient
class WebClientTest {

    private lateinit var webclient: WebTestClient

    @Test
    fun `test webform api`() {
        val bodyBuilder = MultipartBodyBuilder()

        bodyBuilder.part("profileImage", ClassPathResource("test-image.jpg").file.readBytes()).header("Content-Disposition", "form-data; name=profileImage; filename=profile-image.jpg")

        bodyBuilder.part("userDocument", ClassPathResource("test-document.pdf").file.readBytes()).header("Content-Disposition", "form-data; name=userDocument; filename=my-thesis.pdf")

        bodyBuilder.part("username", "shiveenpandita", MediaType.TEXT_PLAIN).header("Content-Disposition", "form-data; name=username").header("Content-type", "text/plain")

        bodyBuilder.part("email", "shiveenpandita@gmail.com", MediaType.TEXT_PLAIN).header("Content-Disposition", "form-data; name=email").header("Content-type", "text/plain")

        webClient.post()
            .uri("/v1/test-api")
            .contentType(MediaType.MULTIPART_FORM_DATA)
            .body(BodyInserters.fromMultipartData(bodyBuilder.build()))
            .exchange()
            .expectStatus().isOk
    }
}
Enter fullscreen mode Exit fullscreen mode

The above code snippet will successfully send the required data to our test api and the webtestclient asserts that the response is 200 OK.

References

Top comments (2)

Collapse
 
saljack profile image
Tomáš Poledný

You can use Resource for file (and setup filename or override getFilename() method) instead header with Content-Disposition and filename

Collapse
 
llain profile image
Christian Jarani

Thanks for sharing, Shaveen. I've recently been working with these and they're a life-saver if you need to send these types of requests.