Play framework - testing forms with array fields
I have an HTML form with a couple of fields, one of which is an array, the other of which is not. I want to be able to test this form with a mock request. The RequestBuilder that comes with Play Framework gives you an either-or sort of option. You can either pass in a “form” with all string fields or with all arrays. Pretty cool. The code looks like this as of 2.5.13ish.
/**
* Set a Form url encoded body to this request.
*
* @param data the x-www-form-urlencoded parameters
* @return the modified builder
*/
public RequestBuilder bodyFormArrayValues(Map<String, String[]> data) {
return body(new RequestBody(data), "application/x-www-form-urlencoded");
}
/**
* Set a Form url encoded body to this request.
*
* @param data the x-www-form-urlencoded parameters
* @return the modified builder
*/
public RequestBuilder bodyForm(Map<String, String> data) {
Map<String, String[]> arrayValues = new HashMap<>();
for (Entry<String, String> entry: data.entrySet()) {
arrayValues.put(entry.getKey(), new String[]{entry.getValue()});
}
return bodyFormArrayValues(arrayValues);
}
I have a MockRequestBuilder that I’ve been using for a bunch of fun things like adding CSRF tokens and authentication bits, etc, so I thought I’d add something else to the mix. Basically, I’m just stealing the code from above and doing some gross-ish instanceof
checks and converting as necessary. Here is what I ended up with.
public MockRequestBuilder withForm(Map<String, Object> formFields) {
Map<String, String[]> arrayForm = convertToArrayForm(formFields);
request = request.bodyFormArrayValues(arrayForm);
return this;
}
private Map<String, String[]> convertToArrayForm(Map<String, Object> formFields) {
Map<String, String[]> arrayFormValues = new HashMap<>();
for (Map.Entry<String, Object> formField : formFields.entrySet()) {
if (formField.getValue() instanceof String) {
String[] value = new String[]{(String) formField.getValue()};
arrayFormValues.put(formField.getKey(), value);
} else if (formField.getValue() instanceof String[]) {
arrayFormValues.put(formField.getKey(), (String[]) formField.getValue());
} else {
throw new RuntimeException("Cannot convert value to form encoding");
}
}
return arrayFormValues;
}
Cool. Now I can write a test like so:
public class MyTest {
@Test
public void testSelectAccounts() {
Map<String, Object> formFields = new LinkedHashMap<>();
formFields.put("selectedAccounts[]", new String[]{userId});
new MockRequestBuilder(
app, routes.MyController.selectAccounts())
.withForm(formFields)
.addToContext();
}
}
And there you have it. For reference, .addToContext()
adds the fake request to the controller context like so (or is it the application context? thread context? I forget what’s happening):
Http.Context httpContext = mock(Http.Context.class);
Http.Context.current.set(httpContext);
when(httpContext.request()).thenReturn(request);
And there you really have it.