改进Spring Mock-MVC测试

Spring Mock- MVC 为Spring Boot REST API提供了出色的测试方法。Mock-MVC允许我们测试Spring-MVC请求处理而无需启动真正的服务器。

我在各种项目上使用过Mock-MVC测试,以我的经验,它们通常很冗长。这不一定是一件坏事。但是,这通常需要将类似的代码片段复制/粘贴到测试类中。在本文中,我们将介绍几种增强Spring Mock-MVC测试的方法。

决定使用Mock-MVC测试什么

我们需要问的第一个问题是我们要使用Mock-MVC测试什么。以下是一些测试用例的示例:

  • 仅测试Web层并模拟所有控制器依赖项。

  • 使用域逻辑测试Web层并模拟第三方依赖性,例如数据库或消息队列。

  • 通过在可能的情况下用嵌入式替代方案(例如H2 或 Embedded-Kafka  )替换第三方依赖项来测试从Web层到数据库的完整路径 

所有这些方案都有其优点和缺点。但是,我认为必须遵循两个简单的规则:

  • 尽可能在标准JUnit测试中进行测试(无Spring)。这大大提高了测试性能,并使编写测试更加容易。

  • 选择要使用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  .

您可以在GitHub上找到代码示例 




All Articles