vendredi 8 mai 2015

Spring Mvc REST api test | jQuery serialized form data-binding

First of all, i'm not a pro with tests, actually i'm learning to use them, and i'm finding myself in a lot of trouble trying to test Spring Controllers with the Spring Test framework, so i did the test after the production code, because i didn't know exactly how build the test.

The project is a Spring Boot (1.2.3) with Web, Thymeleaf, Security, MongoDB and Actuator modules, and using the platform-bom (1.1.2) for version management.

The functionality is easy, while the user fills a form, ajax requests using jQuery are sent to the server to verify the last field the user filled, then the server performs validation, and returns a JSON response.

Client side validation is not an option because some information has to be queried to a database to perform the validation (like if a username is already in use or not), so it needs to be done server side.

Also all the validation is performed with Hibernate Validator and custom annotations, both field and class annotations, since we use 1 entry point to perform the interactive form validation, the entire form is send in each request.

Here is the method signature (and first validations) in a @RestController :

@RequestMapping(value = "/some-url", method = RequestMethod.POST)
public ResponseEntity<ValidationResponse> validateFormField(
     @Valid @ModelAttribute UserRegistrationForm form, BindingResult result) {

    if (form == null || form.getSomeValue() == null) {
        return new ResponseEntity<ValidationResponse>(HttpStatus.BAD_REQUEST);
    }

    // more input validation and actions
}

Now the call code, using jQuery:

$.ajax({
    url: '/some-url',
    type: 'post',
    data: $(selector).serialize(), 
    dataType: 'json',
    ...
});

Now, the unit test code i'm writting to automatic test this is:

@ActiveProfiles("testing")
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {
    Application.class, WebConfiguration.class,
    MongoConfiguration.class, SecurityConfiguration.class
})
@WebAppConfiguration
public class RegistrationInteractiveValidationRestControllerTest {

    @Resource
    private FilterChainProxy springSecurityFilterChain;
    @Autowired
    private WebApplicationContext wac;
    private MockMvc mockMvc;

    @Before
    public void setUp() throws Exception {
        mockMvc = webAppContextSetup(wac)
            .addFilter(springSecurityFilterChain)
            .build();
    }


    @Test
    public void withValidInput_returnStatusOK() throws Exception {

        // test init        
        MockHttpServletRequestBuilder ajaxRequest =  post("/some-url") 
            .content( "username=Bobby&email=& ...more fields... " )
            .secure( true ) .with( csrf().asHeader() ); 
        // test action
        ResultActions performRequest = mockMvc.perform(ajaxRequest);

        // test verification
        performRequest.andExpect( status().isOk() );
    }

    @Test
    public void withInvalidInput_returnStatusBadRequest() throws Exception {

        // test init        
        MockHttpServletRequestBuilder ajaxRequest =  post("/some-url") 
            .content( "{ 'im' : 'a bad content request' }" ) 
            .secure( true ) .with( csrf().asHeader() );

        // test action
        ResultActions performRequest = mockMvc.perform(ajaxRequest);

        // test verification
        performRequest.andExpect(status().isBadRequest());
    }

}

The second test goes alright, status code is 400 as expected, but in the first test, it fails because it also returns a 400.

I did some test debugging and the form object itself is not null, but all its fields are null.

But if i deploy the app in a server, and do the test manually, it works just fine, ajax calls get responses with valid JSON and HTTP 200 status codes.

I also did some debugging of the app deployed in a server and the databinding works just fine.

My thoughts are that i'm missing something building the request in the test, so what i've tried is to verify in the browser the request headers and form data when the app is deployed on a server, and added those to the requests i'm building in the tests, all of them, without any luck at all.

I've researched also the Spring Framework, Spring Boot, Spring security and Spring Test reference documentation, and I didn't find anything that could lead me to the right answer.

Aucun commentaire:

Enregistrer un commentaire