Melhorando os testes Spring Mock-MVC

Spring Mock- MVC  fornece métodos de teste excelentes para APIs REST Spring Boot. O Mock-MVC nos permite testar o processamento de solicitações Spring-MVC sem iniciar um servidor real.

Eu usei testes Mock-MVC em uma variedade de projetos e, na minha experiência, eles costumam ser bastante prolixos. Isso não é necessariamente uma coisa ruim. No entanto, isso geralmente requer a cópia / colagem de fragmentos de código semelhantes em classes de teste. Nesta postagem, veremos várias maneiras de melhorar os testes do Spring Mock-MVC.

Decida o que testar com o Mock-MVC

A primeira pergunta que precisamos fazer é o que queremos testar com o Mock-MVC. Aqui estão alguns exemplos de casos de teste:

  • Testando apenas a camada da web e emulando todas as dependências do controlador.

  • Testar a camada da web com lógica de domínio e simular dependências de terceiros, como bancos de dados ou filas de mensagens.

  • Testar o caminho completo da camada da web para o banco de dados, substituindo dependências de terceiros por alternativas incorporadas, se possível (por exemplo,  H2  ou  Kafka incorporado  )

Todos esses cenários têm seus prós e contras. No entanto, acho que existem duas regras simples que devemos seguir:

  • Teste o máximo possível em testes JUnit padrão (sem Spring). Isso melhora muito o desempenho do teste e torna mais fácil escrevê-los.

  • (-), 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  .

Você pode encontrar os exemplos de código no  GitHub .




All Articles