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();
}