Testing Form posts through MockMVC

Here is a Kotlin SpringBoot example:

@RunWith(MockitoJUnitRunner::class)
class ApiFormControllerTest {

  lateinit var mvc: MockMvc

  @InjectMocks
  lateinit var apiFormController: ApiFormController

  @Before
  fun setup() {
    mvc = MockMvcBuilders.standaloneSetup(apiFormController).setControllerAdvice(ExceptionAdvice()).build()
  }

  fun MockHttpServletRequestBuilder.withForm(params: Map<String, String>): MockHttpServletRequestBuilder {
    this.contentType(MediaType.APPLICATION_FORM_URLENCODED)
        .content(
            EntityUtils.toString(
                UrlEncodedFormEntity(
                    params.entries.toList().map { BasicNameValuePair(it.key, it.value) }
                )
            )
        )
    return this
  }

  @Test
  fun canSubmitValidForm() {
    mvc.perform(post("/forms").withForm(mapOf("subject" to "hello")))
        .andExpect(status().isOk)
  }

}

With modern spring (5.3.12) the offered solution didn't work. There seems to be a simple and elegant solution using the param method in MockHttpServletRequestBuilder:

    mockMvc.perform(post("/some/super/secret/url")
            .param("someparam1", true)
            .param("someparam2", false)
            .with(csrf())
    ).andExpect(status().isOk)

Note: As I am using using Spring Security I needed to add

.with(csrf())

So that the CSRF postProcessor allows my request. If not the Spring security would deny the request to avoid a Cross Site Request Forgery (CSRF) attack.


You could also use this small library I created: https://github.com/f-lopes/spring-mvc-test-utils/.

Add dependency in pom.xml:

<dependency>
    <groupId>io.florianlopes</groupId>
    <artifactId>spring-mvc-test-utils</artifactId>
    <version>1.0.1</version>
    <scope>test</scope>
</dependency>

Use it with MockMvc:

mockMvc.perform(MockMvcRequestBuilderUtils.postForm("/users", new AddUserForm("John", "Doe", null, new Address(1, "Street", 5222, "New York"))))
    .andExpect(MockMvcResultMatchers.status().isFound())
    .andExpect(MockMvcResultMatchers.redirectedUrl("/users"))
    .andExpect(MockMvcResultMatchers.flash().attribute("message", "success"));

This library simply adds the parameters to the MockMvc request, according to the form object.

Here is a detailed tutorial I wrote: https://blog.florianlopes.io/tool-for-spring-mockmvcrequestbuilder-forms-tests/


If you have Apache HTTPComponents HttpClient on your classpath, you can do it like this:

    mockMvc.perform(post("/some/super/secret/url")
            .contentType(MediaType.APPLICATION_FORM_URLENCODED)
            .content(EntityUtils.toString(new UrlEncodedFormEntity(Arrays.asList(
                    new BasicNameValuePair("someparam1", "true"),
                    new BasicNameValuePair("someparam2", "test")
            )))));

If you don't have HttpClient, you can do it with a simple helper method that constructs the urlencoded form entity:

    mockMvc.perform(post("/some/super/secret/url")
            .contentType(MediaType.APPLICATION_FORM_URLENCODED)
            .content(buildUrlEncodedFormEntity(
         "someparam1", "value1", 
         "someparam2", "value2"
    ))));

With this helper function:

private String buildUrlEncodedFormEntity(String... params) {
    if( (params.length % 2) > 0 ) {
       throw new IllegalArgumentException("Need to give an even number of parameters");
    }
    StringBuilder result = new StringBuilder();
    for (int i=0; i<params.length; i+=2) {
        if( i > 0 ) {
            result.append('&');
        }
        try {
            result.
            append(URLEncoder.encode(params[i], StandardCharsets.UTF_8.name())).
            append('=').
            append(URLEncoder.encode(params[i+1], StandardCharsets.UTF_8.name()));
        }
        catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }
    return result.toString();
 }