Teste de Controllers no Spring Boot

Teste de Controllers são aqueles que validam o comportamento do gerenciamento entre as camadas de modelo(Model) e visão(View), com isso, essa camada não deve testar regra de negócio, e sim conversões de formatos do model para view, disponibilização de API, consumo de API, entre outros. Pensando nisso, o Spring Boot disponibiliza algumas anotações para facilitar os testes dessa camada, onde visa simplificar o isolamento da camada de controller em relação as outras camadas, assim possibilitando o teste concentrando nessa camada.

Para utilizar os recursos de testes no Spring Boot, é necessário adicionar a dependência:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>1.5.2.RELEASE</version>
    <scope>test</scope>
</dependency>

A partir da versão 1.4.0 do Spring Boot surgiram algumas anotações como @WebMvcTest e @AutoConfigureMockMvc visando facilitar a configuração para a realização de testes de controllers.

import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@WebMvcTest(HelloController.class)
public class HelloControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void hello() throws Exception {
        mockMvc.perform(get("/hello")
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(content().string("Hello"));
    }

}

No entanto, caso o projeto estiver utilizando JPA, como por exemplo a anotação @EnableJpaRepositories do Spring data, ocorre um conflito de configuração quando utilizando a anotação @WebMvcTest impedindo o funcionamento do teste, pois essa anotação desabilita o funcionamento do JPA.

Exemplo do erro ocasionado pela anotação @EnableJpaRepositories

Caused by: java.lang.IllegalArgumentException: At least one JPA metamodel must be present!
	at org.springframework.util.Assert.notEmpty(Assert.java:276)
	at org.springframework.data.jpa.mapping.JpaMetamodelMappingContext.&amp;amp;lt;init&amp;amp;gt;(JpaMetamodelMappingContext.java:52)
	at org.springframework.data.jpa.repository.config.JpaMetamodelMappingContextFactoryBean.createInstance(JpaMetamodelMappingContextFactoryBean.java:71)
	at org.springframework.data.jpa.repository.config.JpaMetamodelMappingContextFactoryBean.createInstance(JpaMetamodelMappingContextFactoryBean.java:26)
	at org.springframework.beans.factory.config.AbstractFactoryBean.afterPropertiesSet(AbstractFactoryBean.java:134)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1642)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1579)

Exemplo da configuração do Spring Boot que ocasionou o erro no momento do teste

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@SpringBootApplication
@ComponentScan(basePackageClasses = {HelloService.class, HelloController.class})
@EnableJpaRepositories(basePackageClasses = {HelloRepository.class})
public class AppConfig {

    public static void main(String[] args) {
        ApplicationContext ctx = SpringApplication.run(AppConfig.class, args);
    }
}

Observação: Esse problema citado acima ocorreu nas versão 1.4.0 e 1.5.2 do Spring Boot.

Com isso, quando houver configurações de JPA, como no exemplo a cima, é possível testar os controllers utilizando as anotações @SpringBootTest e @AutoConfigureMockMvc, como demonstrado a baixo:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class HelloControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void hello() throws Exception {
        mockMvc.perform(get("/hello")
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(content().string("Hello"));
    }

}

Durante os testes de controller não deve-se utilizar a camada de dados, com isso, caso o Controller a ser testado tenha alguma dependência dessa camada é possível mocar o comportamento dessas camadas, segue o exemplo de como mocar o funcionamento do serviço:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class HelloControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private HelloService helloService;

    @Test
    public void listar() throws Exception {
        given(helloService.listar()).willReturn(new Hello("Hello"));

        mockMvc.perform(get("/hello/listar")
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(content().string("Hello"));
    }

}

Caso esteja utilizando uma versão anterior a 1.4.0 do Spring Boot é possível testar controller em conjunto com o Mockito, como no exemplo a seguir:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootConfiguration
public class HelloControllerTest {

    private MockMvc mockMvc;

    @InjectMocks
    private HelloController helloController;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        mockMvc = MockMvcBuilders.standaloneSetup(helloController).build();
    }

    @Test
    public void hello() throws Exception {
        mockMvc.perform(get("/hello")
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(content().string("Hello"));
    }
}

Diferente das versões mais atuais do Spring Boot, é preciso utilizar o Mockito para injetar o controller no contexto do teste e posteriormente inicializar o mockMvc através de builders do Spring.

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s