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.