Diferença entre usar MockMvc com SpringBootTest e usar WebMvcTest

98

Eu sou novo no Spring Boot e estou tentando entender como funcionam os testes no SpringBoot. Estou um pouco confuso sobre qual é a diferença entre os dois seguintes snippets de código:

Snippet de código 1:

@RunWith(SpringRunner.class)
@WebMvcTest(HelloController.class)
public class HelloControllerApplicationTest {
    @Autowired    
    private MockMvc mvc;

    @Test
    public void getHello() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(content().string(equalTo("Greetings from Spring Boot!")));
    }
}

Este teste usa a @WebMvcTestanotação que acredito ser para teste de fatia de recurso e testa apenas a camada MVC de um aplicativo da web.

Snippet de código 2:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class HelloControllerTest {

    @Autowired
    private MockMvc mvc;

    @Test
    public void getHello() throws Exception {
    mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(content().string(equalTo("Greetings from Spring Boot!")));
    }
}

Este teste usa a @SpringBootTestanotação e a MockMvc. Então, como isso é diferente do trecho de código 1? O que isso faz de diferente?

Edit: Adicionando trecho de código 3 (encontrado como um exemplo de teste de integração na documentação do Spring)

@RunWith(SpringRunner.class) 
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 
public class HelloControllerIT {
    
    @LocalServerPort private int port;
    private URL base;
    
    @Autowired private TestRestTemplate template;
    
    @Before public void setUp() throws Exception {
        this.base = new URL("http://localhost:" + port + "/");
    }
    
    @Test public void getHello() throws Exception {
        ResponseEntity < String > response = template.getForEntity(base.toString(), String.class);
        assertThat(response.getBody(), equalTo("Greetings from Spring Boot!"));
    }
}
Revansha
fonte

Respostas:

89

@SpringBootTesté a anotação geral do teste. Se você está procurando por algo que faça a mesma coisa antes de 1.4, é o que você deve usar. Ele não usa fatiamento , o que significa que iniciará todo o contexto do aplicativo e não personalizará a varredura de componentes de forma alguma.

@WebMvcTestvai apenas varrer o controlador que você definiu e a infraestrutura MVC. É isso aí. Portanto, se seu controlador tiver alguma dependência de outros beans de sua camada de serviço, o teste não será iniciado até que você mesmo carregue essa configuração ou forneça uma simulação para ela. Isso é muito mais rápido, pois carregamos apenas uma pequena parte do seu aplicativo. Esta anotação usa fatiamento.

Ler o documento provavelmente também deve ajudá-lo.

Stephane Nicoll
fonte
Muito obrigado por responder !!. Então, se bem entendi, o que isso significa é que ambos os trechos de código testam apenas a parte MVC do aplicativo. Mas o trecho de código 1 carrega todo o contexto do aplicativo, enquanto o trecho de código 2 verifica apenas o controlador. Isso está correto? O snippet de código 1 pode ser considerado um teste de unidade para testar o controlador?
Revansha
1
Não, não está correto. SpringBootTestestá carregando seu aplicativo completo (até certo ponto, por padrão, ele não iniciará o contêiner incorporado se houver um disponível, é para isso que webEnvironmentexiste). Eu não diria que @SpringBootTesté um teste de unidade do controlador, mas mais um teste de integração, na verdade. WebMvcTesté realmente um teste de unidade do seu controlador no sentido de que, se ele tiver dependência, você terá que fornecê-los você mesmo (uma configuração ou algum tipo de simulação).
Stephane Nicoll,
Obrigado novamente por responder. Editei a pergunta e adicionei o trecho de código 3. Você mencionou que a anotação @SpringBootTest é mais usada para testes de integração. Eu acredito que o Snippet 3 demonstra isso. Portanto, se o teste de integração for feito como no Snippet 3, o que o Snippet 2 faz? O snippet 2 usa a anotação SpringBootTest e um ambiente fictício (valor padrão do atributo wenEnvironment). Além disso, o snippet 3 inicia o servidor integrado e faz chamadas realmente HTTP, enquanto o snippet 2 não faz isso. Considerando isso, o snippet 2 não pode ser considerado um teste de unidade?
Revansha
4
Não tenho certeza se vamos resolver isso aqui. Talvez gitter? O que você parece perder constantemente é que o contexto do aplicativo criado SpringBootTeste WebMvcTestcriado são muito diferentes. O primeiro carrega TODO o seu aplicativo e ativa TODAS as configurações automáticas, enquanto o último só ativa o Spring Mvc e não verifica nada além de HelloController. Afinal, tudo depende do que você entende por teste de unidade. Mas essa é a diferença.
Stephane Nicoll,
Obrigado pela sua resposta. Isso é muito útil para mim. Agora eu entendo porque meu teste pode ser executado com SpringBootTest, mas exceção quando WebMvcTest. Muito obrigado novamente.
Alps 1992
71

A anotação @SpringBootTest diz ao Spring Boot para ir e procurar por uma classe de configuração principal (uma com @SpringBootApplication por exemplo) e usá-la para iniciar um contexto de aplicativo Spring. SpringBootTest carrega o aplicativo completo e injeta todos os beans que podem ser lentos.

@WebMvcTest - para testar a camada do controlador e você precisa fornecer as dependências restantes necessárias usando objetos Mock.

Mais algumas anotações abaixo para sua referência.

Testando fatias do aplicativo Às vezes, você gostaria de testar uma simples “fatia” do aplicativo em vez de configurar automaticamente todo o aplicativo. Spring Boot 1.4 apresenta 4 novas anotações de teste:

@WebMvcTest - for testing the controller layer
@JsonTest - for testing the JSON marshalling and unmarshalling
@DataJpaTest - for testing the repository layer
@RestClientTests - for testing REST clients

Consulte para mais informações: https://spring.io/guides/gs/testing-web/

RoshanKumar Mutha
fonte
Aqui está um link para Sping Boot Reference - Testar anotações de configuração automática . Existem mais do que apenas os quatro @ roshankumar-mutha listados aqui. O link para o guia de primeiros passos não cobre essas fatias em detalhes.
George Pantazes
15

Os testes MVC têm como objetivo cobrir apenas a parte do controlador de sua aplicação. Solicitações e respostas HTTP são simuladas para que as conexões reais não sejam criadas. Por outro lado, ao usar @SpringBootTest, toda a configuração do contexto da aplicação web é carregada e as conexões estão passando pelo servidor web real. Nesse caso, você não usa o MockMvcbean, mas um padrão RestTemplate(ou a nova alternativa TestRestTemplate).

Então, quando devemos escolher um ou outro? @WebMvcTestdestina-se a testar unitariamente o controlador do lado do servidor. @SpringBootTest, por outro lado, deve ser usado para testes de integração, quando se deseja interagir com a aplicação do lado do cliente.

Isso não significa que você não pode usar mocks com @SpringBootTest; se você estiver escrevendo um teste de integração, isso ainda pode ser necessário. Em qualquer caso, é melhor não usá-lo apenas para um simples teste de unidade do controlador.

fonte - Aprendendo microsserviços com Spring Boot

Gautam Tadigoppula
fonte
4
Não entendo porque esta resposta foi votada positivamente. Quando você usa @SpringBootTest, um servidor web real não é iniciado a menos que você também tenha webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT(ou a DEFINED_PORT) e as conexões não passem pelo servidor web real. O padrão para @SpringBootTesté WebEnvironment.MOCK.
Koray Tugay