Spring Mock- MVC provides excellent testing methods for Spring Boot REST APIs. Mock-MVC allows us to test Spring-MVC request handling without starting a real server.
I've used Mock-MVC tests on various projects and in my experience they are often quite verbose. This isn't necessarily a bad thing. However, this often requires copying / pasting similar code fragments into test classes. In this post, we'll take a look at several ways to improve Spring Mock-MVC tests.
Decide what to test with Mock-MVC
The first question we need to ask is what we want to test with Mock-MVC. Here are some examples of test cases:
Testing only the web layer and emulating all controller dependencies.
Testing the web tier with domain logic and simulating third-party dependencies such as databases or message queues.
Testing the full path from the web layer to the database by replacing third-party dependencies with embedded alternatives if possible (e.g. H2 or embedded-Kafka )
All of these scenarios have their pros and cons. However, I think there are two simple rules that we must follow:
Test as much as possible in standard JUnit tests (no Spring). This greatly improves test performance and makes it easier to write tests.
(-), Spring, , . . Spring , .
JUnit . , , Mock-MVC, , , .
Spring Spring .
, @MockMvcTest:
@SpringBootTest
@TestPropertySource(locations = "classpath:test.properties")
@AutoConfigureMockMvc(secure = false)
@Retention(RetentionPolicy.RUNTIME)
public @interface MockMvcTest {}
:
@MockMvcTest
public class MyTest {
...
}
, . Spring .
Mock-MVC
Mock-MVC , :
mockMvc.perform(put("/products/42")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content("{\"name\": \"Cool Gadget\", \"description\": \"Looks cool\"}")
.header("Authorization", getBasicAuthHeader("John", "secr3t")))
.andExpect(status().isOk());
PUT JSON /products/42.
, - JSON Java. , , , , Java, .
, JSON. , . Java Text JDK 13/14 . - , .
JSON . :
mvc.perform(put("/products/42")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content("""
{
"name": "Cool Gadget",
"description": "Looks cool"
}
""")
.header("Authorization", getBasicAuthHeader("John", "secr3t")))
.andExpect(status().isOk());
.
, -, , JSON , JSON.
:
Product product = new Product("Cool Gadget", "Looks cool");
mvc.perform(put("/products/42")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content(objectToJson(product))
.header("Authorization", getBasicAuthHeader("John", "secr3t")))
.andExpect(status().isOk());
product JSON objectToJson(..). . , .
, . JSON REST-API, , PUT. :
public static MockHttpServletRequestBuilder putJson(String uri, Object body) {
try {
String json = new ObjectMapper().writeValueAsString(body);
return put(uri)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content(json);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
body JSON Jackson ObjectMapper . PUT Accept Content-Type .
:
Product product = new Product("Cool Gadget", "Looks cool");
mvc.perform(putJson("/products/42", product)
.header("Authorization", getBasicAuthHeader("John", "secr3t")))
.andExpect(status().isOk())
, . putJson(..) MockHttpServletRequestBuilder. , , (, ).
- , Spring Mock-MVC. putJson(..). PUT , , -.
RequestPostProcessor . , RequestPostProcessor . .
:
public static RequestPostProcessor authentication() {
return request -> {
request.addHeader("Authorization", getBasicAuthHeader("John", "secr3t"));
return request;
};
}
authentication() RequestPostProcessor, . RequestPostProcessor with(..):
Product product = new Product("Cool Gadget", "Looks cool");
mvc.perform(putJson("/products/42", product).with(authentication()))
.andExpect(status().isOk())
. , , . , putJson(url, data).with(authentication()) .
, .
:
mvc.perform(get("/products/42"))
.andExpect(status().isOk())
.andExpect(header().string("Cache-Control", "no-cache"))
.andExpect(jsonPath("$.name").value("Cool Gadget"))
.andExpect(jsonPath("$.description").value("Looks cool"));
HTTP, , Cache-Control no-cache, JSON-Path .
Cache-Control , , , . :
public ResultMatcher noCacheHeader() {
return header().string("Cache-Control", "no-cache");
}
, noCacheHeader() andExpect(..):
mvc.perform(get("/products/42"))
.andExpect(status().isOk())
.andExpect(noCacheHeader())
.andExpect(jsonPath("$.name").value("Cool Gadget"))
.andExpect(jsonPath("$.description").value("Looks cool"));
.
, product(..), JSON Product:
public static ResultMatcher product(String prefix, Product product) {
return ResultMatcher.matchAll(
jsonPath(prefix + ".name").value(product.getName()),
jsonPath(prefix + ".description").value(product.getDescription())
);
}
:
Product product = new Product("Cool Gadget", "Looks cool");
mvc.perform(get("/products/42"))
.andExpect(status().isOk())
.andExpect(noCacheHeader())
.andExpect(product("$", product));
, prefix . , , JSON .
, . prefix . :
Product product0 = ..
Product product1 = ..
mvc.perform(get("/products"))
.andExpect(status().isOk())
.andExpect(product("$[0]", product0))
.andExpect(product("$[1]", product1));
ResultMatcher . .
Spring Mock-MVC. Mock-MVC, , . ( Spring Mock-MVC).
Spring Mock-MVC. RequestPostProcessor . ResultMatcher .
You can find the code examples on GitHub .