Testing Spring Rest controllers: simpler, shorter, more reliable. Spring Security Test + JSON Matcher

  • Tutorial


Hello!
Native JSON Matcher, the use of Spring Security Test , recently included in Spring Security 4.0, and the rejection of transactions when testing services make the tests easier and more reliable.

I used this approach when creating an application on my Topjava course (Maven / Spring / Security / JPA (Hibernate) / Rest (Jackson) / Bootstrap (CSS) / jQuery + plugins), the project source code can be taken on github .

This article is not yet another tutorial on testing Spring REST controllers and suggests that you are already familiar with it.

I already wrote about the benefits of refusing transactions in a previous publicationIn the wake of Spring Pet Clinic. Maven / Spring Context / Spring Test / Spring ORM / Spring Data JPA . To the listed disadvantages of using @Transaction you can add the inability to look at real queries when executing a service method to the database (for example, with hibernate.use_sql_comments turned on)

Here I will write how it is easier, shorter, more reliable to test Spring REST controllers.

Spring security test


Prior to the release of Spring Security 4.0, this was a separate project that was included in Spring Security 4.0 as Improved Testing Support.
Now it is in the central maven repository and connects like this:
4.0.1.RELEASEorg.springframework.securityspring-security-test${spring-security.version}

The general code for testing controllers can be made an abstract class .
Connecting security to Spring MVC tests has become a little easier:
    mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
             .apply(springSecurity()).build();

For REST services, the project uses http-basic, whose support in the Spring Security Test is among many others.
For convenience, we create test data and make httpBasic connection even easier:
public static RequestPostProcessor userHttpBasic(User user) {
   return SecurityMockMvcRequestPostProcessors
           .httpBasic(user.getEmail(), user.getPassword());
}
mockMvc.perform(get(REST_URL).contentType(MediaType.APPLICATION_JSON)
       .with(userHttpBasic(ADMIN)))
       .andExpect(...

At the same time, the security context is not changed in the tests, as had to be done before , but the httpBasic "Authorization" header is honestly set.

Checking JSON content of a response through its own ResultMatcher


A common practice in all REST testing guides: using json-path to selectively check the fields of the content of the json response (there is usually not enough patience for a full check). Or the use of more advanced libraries , which does not change the essence of the approach.

However, it is much better to immediately compare the entire object with all the nested levels of the hierarchy! Because You cannot compare the string representation of JSON objects (it can have different formatting and field order), you need to serialize the content of the JSON response into an object and compare the objects. Spring MVC is integrated with the Jackson JSON library , so we can configure ObjectMapper as we like (for example, disableserialization of lazy loading of fields of Hibernate objects ) and make a convenient utility class for JSON serialization-deserialization .

For use in Spring Test syntax tests:
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))

we make our own ResultMatcher with the methods contentMatcher and contentListMatcher comparing objects and a list of objects, respectively. It’s enough to configure this Matcher to work with the necessary objects (in the simplest case, if the equals method of the ORM object is made according to PK equality and cannot be used for comparison, you can compare objects via toString ):
public static final ModelMatcher MATCHER = 
                      new ToStringModelMatcher<>(UserMeal.class);

For more complex cases, for comparison, you will have to make a wrapper over the ORM object .
But now, tests for checking the JSON object in the REST response with any level of attachment of the JSON object will look very simple:

mockMvc.perform(get(REST_URL + "by?email=" + USER.getEmail())
       .with(userHttpBasic(ADMIN)))
       .andExpect(MATCHER.contentMatcher(USER))
       .andExpect(...

The REST tests of the project controllers are configured in hsqldb in-memory, so they can be run without connecting to the DB.
I hope that these solutions come in handy and thank you for your attention.

Also popular now: